
const validation: point at where we found a pointer but expected an integer Instead of validation just printing "unable to turn pointer into bytes", make this a regular validation error that says where in the value the bad pointer was found. Also distinguish "expected integer, got pointer" from "expected pointer, got partial pointer or mix of pointers". To avoid duplicating things too much I refactored the diagnostics for validity a bit, so that "got uninit, expected X" and "got pointer, expected X" can share the "X" part. Also all the errors emitted for validation are now grouped under `const_eval_validation` so that they are in a single group in the ftl file. r? `@oli-obk`
500 lines
17 KiB
Rust
500 lines
17 KiB
Rust
use super::{AllocId, AllocRange, ConstAlloc, Pointer, Scalar};
|
|
|
|
use crate::mir::interpret::ConstValue;
|
|
use crate::query::TyCtxtAt;
|
|
use crate::ty::{layout, tls, Ty, ValTree};
|
|
|
|
use rustc_data_structures::sync::Lock;
|
|
use rustc_errors::{
|
|
struct_span_err, DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, ErrorGuaranteed,
|
|
IntoDiagnosticArg,
|
|
};
|
|
use rustc_macros::HashStable;
|
|
use rustc_session::CtfeBacktrace;
|
|
use rustc_span::def_id::DefId;
|
|
use rustc_target::abi::{call, Align, Size, VariantIdx, WrappingRange};
|
|
|
|
use std::borrow::Cow;
|
|
use std::{any::Any, backtrace::Backtrace, fmt};
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
|
|
pub enum ErrorHandled {
|
|
/// Already reported an error for this evaluation, and the compilation is
|
|
/// *guaranteed* to fail. Warnings/lints *must not* produce `Reported`.
|
|
Reported(ReportedErrorInfo),
|
|
/// Don't emit an error, the evaluation failed because the MIR was generic
|
|
/// and the args didn't fully monomorphize it.
|
|
TooGeneric,
|
|
}
|
|
|
|
impl From<ErrorGuaranteed> for ErrorHandled {
|
|
#[inline]
|
|
fn from(error: ErrorGuaranteed) -> ErrorHandled {
|
|
ErrorHandled::Reported(error.into())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
|
|
pub struct ReportedErrorInfo {
|
|
error: ErrorGuaranteed,
|
|
is_tainted_by_errors: bool,
|
|
}
|
|
|
|
impl ReportedErrorInfo {
|
|
#[inline]
|
|
pub fn tainted_by_errors(error: ErrorGuaranteed) -> ReportedErrorInfo {
|
|
ReportedErrorInfo { is_tainted_by_errors: true, error }
|
|
}
|
|
|
|
/// Returns true if evaluation failed because MIR was tainted by errors.
|
|
#[inline]
|
|
pub fn is_tainted_by_errors(self) -> bool {
|
|
self.is_tainted_by_errors
|
|
}
|
|
}
|
|
|
|
impl From<ErrorGuaranteed> for ReportedErrorInfo {
|
|
#[inline]
|
|
fn from(error: ErrorGuaranteed) -> ReportedErrorInfo {
|
|
ReportedErrorInfo { is_tainted_by_errors: false, error }
|
|
}
|
|
}
|
|
|
|
impl Into<ErrorGuaranteed> for ReportedErrorInfo {
|
|
#[inline]
|
|
fn into(self) -> ErrorGuaranteed {
|
|
self.error
|
|
}
|
|
}
|
|
|
|
TrivialTypeTraversalAndLiftImpls! { ErrorHandled }
|
|
|
|
pub type EvalToAllocationRawResult<'tcx> = Result<ConstAlloc<'tcx>, ErrorHandled>;
|
|
pub type EvalToConstValueResult<'tcx> = Result<ConstValue<'tcx>, ErrorHandled>;
|
|
pub type EvalToValTreeResult<'tcx> = Result<Option<ValTree<'tcx>>, ErrorHandled>;
|
|
|
|
pub fn struct_error<'tcx>(
|
|
tcx: TyCtxtAt<'tcx>,
|
|
msg: &str,
|
|
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
|
|
struct_span_err!(tcx.sess, tcx.span, E0080, "{}", msg)
|
|
}
|
|
|
|
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
|
|
static_assert_size!(InterpErrorInfo<'_>, 8);
|
|
|
|
/// Packages the kind of error we got from the const code interpreter
|
|
/// up with a Rust-level backtrace of where the error occurred.
|
|
/// These should always be constructed by calling `.into()` on
|
|
/// an `InterpError`. In `rustc_mir::interpret`, we have `throw_err_*`
|
|
/// macros for this.
|
|
#[derive(Debug)]
|
|
pub struct InterpErrorInfo<'tcx>(Box<InterpErrorInfoInner<'tcx>>);
|
|
|
|
#[derive(Debug)]
|
|
struct InterpErrorInfoInner<'tcx> {
|
|
kind: InterpError<'tcx>,
|
|
backtrace: InterpErrorBacktrace,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct InterpErrorBacktrace {
|
|
backtrace: Option<Box<Backtrace>>,
|
|
}
|
|
|
|
impl InterpErrorBacktrace {
|
|
pub fn new() -> InterpErrorBacktrace {
|
|
let capture_backtrace = tls::with_opt(|tcx| {
|
|
if let Some(tcx) = tcx {
|
|
*Lock::borrow(&tcx.sess.ctfe_backtrace)
|
|
} else {
|
|
CtfeBacktrace::Disabled
|
|
}
|
|
});
|
|
|
|
let backtrace = match capture_backtrace {
|
|
CtfeBacktrace::Disabled => None,
|
|
CtfeBacktrace::Capture => Some(Box::new(Backtrace::force_capture())),
|
|
CtfeBacktrace::Immediate => {
|
|
// Print it now.
|
|
let backtrace = Backtrace::force_capture();
|
|
print_backtrace(&backtrace);
|
|
None
|
|
}
|
|
};
|
|
|
|
InterpErrorBacktrace { backtrace }
|
|
}
|
|
|
|
pub fn print_backtrace(&self) {
|
|
if let Some(backtrace) = self.backtrace.as_ref() {
|
|
print_backtrace(backtrace);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'tcx> InterpErrorInfo<'tcx> {
|
|
pub fn into_parts(self) -> (InterpError<'tcx>, InterpErrorBacktrace) {
|
|
let InterpErrorInfo(box InterpErrorInfoInner { kind, backtrace }) = self;
|
|
(kind, backtrace)
|
|
}
|
|
|
|
pub fn into_kind(self) -> InterpError<'tcx> {
|
|
let InterpErrorInfo(box InterpErrorInfoInner { kind, .. }) = self;
|
|
kind
|
|
}
|
|
|
|
#[inline]
|
|
pub fn kind(&self) -> &InterpError<'tcx> {
|
|
&self.0.kind
|
|
}
|
|
}
|
|
|
|
fn print_backtrace(backtrace: &Backtrace) {
|
|
eprintln!("\n\nAn error occurred in the MIR interpreter:\n{backtrace}");
|
|
}
|
|
|
|
impl From<ErrorGuaranteed> for InterpErrorInfo<'_> {
|
|
fn from(err: ErrorGuaranteed) -> Self {
|
|
InterpError::InvalidProgram(InvalidProgramInfo::AlreadyReported(err.into())).into()
|
|
}
|
|
}
|
|
|
|
impl<'tcx> From<InterpError<'tcx>> for InterpErrorInfo<'tcx> {
|
|
fn from(kind: InterpError<'tcx>) -> Self {
|
|
InterpErrorInfo(Box::new(InterpErrorInfoInner {
|
|
kind,
|
|
backtrace: InterpErrorBacktrace::new(),
|
|
}))
|
|
}
|
|
}
|
|
|
|
/// Error information for when the program we executed turned out not to actually be a valid
|
|
/// program. This cannot happen in stand-alone Miri, but it can happen during CTFE/ConstProp
|
|
/// where we work on generic code or execution does not have all information available.
|
|
#[derive(Debug)]
|
|
pub enum InvalidProgramInfo<'tcx> {
|
|
/// Resolution can fail if we are in a too generic context.
|
|
TooGeneric,
|
|
/// Abort in case errors are already reported.
|
|
AlreadyReported(ReportedErrorInfo),
|
|
/// An error occurred during layout computation.
|
|
Layout(layout::LayoutError<'tcx>),
|
|
/// An error occurred during FnAbi computation: the passed --target lacks FFI support
|
|
/// (which unfortunately typeck does not reject).
|
|
/// Not using `FnAbiError` as that contains a nested `LayoutError`.
|
|
FnAbiAdjustForForeignAbi(call::AdjustForForeignAbiError),
|
|
/// SizeOf of unsized type was requested.
|
|
SizeOfUnsizedType(Ty<'tcx>),
|
|
/// We are runnning into a nonsense situation due to ConstProp violating our invariants.
|
|
ConstPropNonsense,
|
|
}
|
|
|
|
/// Details of why a pointer had to be in-bounds.
|
|
#[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)]
|
|
pub enum CheckInAllocMsg {
|
|
/// We are dereferencing a pointer (i.e., creating a place).
|
|
DerefTest,
|
|
/// We are access memory.
|
|
MemoryAccessTest,
|
|
/// We are doing pointer arithmetic.
|
|
PointerArithmeticTest,
|
|
/// We are doing pointer offset_from.
|
|
OffsetFromTest,
|
|
/// None of the above -- generic/unspecific inbounds test.
|
|
InboundsTest,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)]
|
|
pub enum InvalidMetaKind {
|
|
/// Size of a `[T]` is too big
|
|
SliceTooBig,
|
|
/// Size of a DST is too big
|
|
TooBig,
|
|
}
|
|
|
|
impl IntoDiagnosticArg for InvalidMetaKind {
|
|
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
|
|
DiagnosticArgValue::Str(Cow::Borrowed(match self {
|
|
InvalidMetaKind::SliceTooBig => "slice_too_big",
|
|
InvalidMetaKind::TooBig => "too_big",
|
|
}))
|
|
}
|
|
}
|
|
|
|
/// Details of an access to uninitialized bytes / bad pointer bytes where it is not allowed.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct BadBytesAccess {
|
|
/// Range of the original memory access.
|
|
pub access: AllocRange,
|
|
/// Range of the bad memory that was encountered. (Might not be maximal.)
|
|
pub bad: AllocRange,
|
|
}
|
|
|
|
/// Information about a size mismatch.
|
|
#[derive(Debug)]
|
|
pub struct ScalarSizeMismatch {
|
|
pub target_size: u64,
|
|
pub data_size: u64,
|
|
}
|
|
|
|
macro_rules! impl_into_diagnostic_arg_through_debug {
|
|
($($ty:ty),*$(,)?) => {$(
|
|
impl IntoDiagnosticArg for $ty {
|
|
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
|
|
DiagnosticArgValue::Str(Cow::Owned(format!("{self:?}")))
|
|
}
|
|
}
|
|
)*}
|
|
}
|
|
|
|
// These types have nice `Debug` output so we can just use them in diagnostics.
|
|
impl_into_diagnostic_arg_through_debug! {
|
|
AllocId,
|
|
Pointer,
|
|
AllocRange,
|
|
}
|
|
|
|
/// Error information for when the program caused Undefined Behavior.
|
|
#[derive(Debug)]
|
|
pub enum UndefinedBehaviorInfo<'a> {
|
|
/// Free-form case. Only for errors that are never caught! Used by miri
|
|
Ub(String),
|
|
/// Unreachable code was executed.
|
|
Unreachable,
|
|
/// A slice/array index projection went out-of-bounds.
|
|
BoundsCheckFailed { len: u64, index: u64 },
|
|
/// Something was divided by 0 (x / 0).
|
|
DivisionByZero,
|
|
/// Something was "remainded" by 0 (x % 0).
|
|
RemainderByZero,
|
|
/// Signed division overflowed (INT_MIN / -1).
|
|
DivisionOverflow,
|
|
/// Signed remainder overflowed (INT_MIN % -1).
|
|
RemainderOverflow,
|
|
/// Overflowing inbounds pointer arithmetic.
|
|
PointerArithOverflow,
|
|
/// Invalid metadata in a wide pointer
|
|
InvalidMeta(InvalidMetaKind),
|
|
/// Reading a C string that does not end within its allocation.
|
|
UnterminatedCString(Pointer),
|
|
/// Using a pointer after it got freed.
|
|
PointerUseAfterFree(AllocId, CheckInAllocMsg),
|
|
/// Used a pointer outside the bounds it is valid for.
|
|
/// (If `ptr_size > 0`, determines the size of the memory range that was expected to be in-bounds.)
|
|
PointerOutOfBounds {
|
|
alloc_id: AllocId,
|
|
alloc_size: Size,
|
|
ptr_offset: i64,
|
|
ptr_size: Size,
|
|
msg: CheckInAllocMsg,
|
|
},
|
|
/// Using an integer as a pointer in the wrong way.
|
|
DanglingIntPointer(u64, CheckInAllocMsg),
|
|
/// Used a pointer with bad alignment.
|
|
AlignmentCheckFailed { required: Align, has: Align },
|
|
/// Writing to read-only memory.
|
|
WriteToReadOnly(AllocId),
|
|
/// Trying to access the data behind a function pointer.
|
|
DerefFunctionPointer(AllocId),
|
|
/// Trying to access the data behind a vtable pointer.
|
|
DerefVTablePointer(AllocId),
|
|
/// Using a non-boolean `u8` as bool.
|
|
InvalidBool(u8),
|
|
/// Using a non-character `u32` as character.
|
|
InvalidChar(u32),
|
|
/// The tag of an enum does not encode an actual discriminant.
|
|
InvalidTag(Scalar),
|
|
/// Using a pointer-not-to-a-function as function pointer.
|
|
InvalidFunctionPointer(Pointer),
|
|
/// Using a pointer-not-to-a-vtable as vtable pointer.
|
|
InvalidVTablePointer(Pointer),
|
|
/// Using a string that is not valid UTF-8,
|
|
InvalidStr(std::str::Utf8Error),
|
|
/// Using uninitialized data where it is not allowed.
|
|
InvalidUninitBytes(Option<(AllocId, BadBytesAccess)>),
|
|
/// Working with a local that is not currently live.
|
|
DeadLocal,
|
|
/// Data size is not equal to target size.
|
|
ScalarSizeMismatch(ScalarSizeMismatch),
|
|
/// A discriminant of an uninhabited enum variant is written.
|
|
UninhabitedEnumVariantWritten(VariantIdx),
|
|
/// An uninhabited enum variant is projected.
|
|
UninhabitedEnumVariantRead(VariantIdx),
|
|
/// Validation error.
|
|
ValidationError(ValidationErrorInfo<'a>),
|
|
// FIXME(fee1-dead) these should all be actual variants of the enum instead of dynamically
|
|
// dispatched
|
|
/// A custom (free-form) error, created by `err_ub_custom!`.
|
|
Custom(crate::error::CustomSubdiagnostic<'a>),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum PointerKind {
|
|
Ref,
|
|
Box,
|
|
}
|
|
|
|
impl IntoDiagnosticArg for PointerKind {
|
|
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
|
|
DiagnosticArgValue::Str(
|
|
match self {
|
|
Self::Ref => "ref",
|
|
Self::Box => "box",
|
|
}
|
|
.into(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ValidationErrorInfo<'tcx> {
|
|
pub path: Option<String>,
|
|
pub kind: ValidationErrorKind<'tcx>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ExpectedKind {
|
|
Reference,
|
|
Box,
|
|
RawPtr,
|
|
InitScalar,
|
|
Bool,
|
|
Char,
|
|
Float,
|
|
Int,
|
|
FnPtr,
|
|
EnumTag,
|
|
Str,
|
|
}
|
|
|
|
impl From<PointerKind> for ExpectedKind {
|
|
fn from(x: PointerKind) -> ExpectedKind {
|
|
match x {
|
|
PointerKind::Box => ExpectedKind::Box,
|
|
PointerKind::Ref => ExpectedKind::Reference,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ValidationErrorKind<'tcx> {
|
|
PointerAsInt { expected: ExpectedKind },
|
|
PartialPointer,
|
|
PtrToUninhabited { ptr_kind: PointerKind, ty: Ty<'tcx> },
|
|
PtrToStatic { ptr_kind: PointerKind },
|
|
PtrToMut { ptr_kind: PointerKind },
|
|
MutableRefInConst,
|
|
NullFnPtr,
|
|
NeverVal,
|
|
NullablePtrOutOfRange { range: WrappingRange, max_value: u128 },
|
|
PtrOutOfRange { range: WrappingRange, max_value: u128 },
|
|
OutOfRange { value: String, range: WrappingRange, max_value: u128 },
|
|
UnsafeCell,
|
|
UninhabitedVal { ty: Ty<'tcx> },
|
|
InvalidEnumTag { value: String },
|
|
UninhabitedEnumVariant,
|
|
Uninit { expected: ExpectedKind },
|
|
InvalidVTablePtr { value: String },
|
|
InvalidMetaSliceTooLarge { ptr_kind: PointerKind },
|
|
InvalidMetaTooLarge { ptr_kind: PointerKind },
|
|
UnalignedPtr { ptr_kind: PointerKind, required_bytes: u64, found_bytes: u64 },
|
|
NullPtr { ptr_kind: PointerKind },
|
|
DanglingPtrNoProvenance { ptr_kind: PointerKind, pointer: String },
|
|
DanglingPtrOutOfBounds { ptr_kind: PointerKind },
|
|
DanglingPtrUseAfterFree { ptr_kind: PointerKind },
|
|
InvalidBool { value: String },
|
|
InvalidChar { value: String },
|
|
InvalidFnPtr { value: String },
|
|
}
|
|
|
|
/// Error information for when the program did something that might (or might not) be correct
|
|
/// to do according to the Rust spec, but due to limitations in the interpreter, the
|
|
/// operation could not be carried out. These limitations can differ between CTFE and the
|
|
/// Miri engine, e.g., CTFE does not support dereferencing pointers at integral addresses.
|
|
#[derive(Debug)]
|
|
pub enum UnsupportedOpInfo {
|
|
/// Free-form case. Only for errors that are never caught!
|
|
// FIXME still use translatable diagnostics
|
|
Unsupported(String),
|
|
//
|
|
// The variants below are only reachable from CTFE/const prop, miri will never emit them.
|
|
//
|
|
/// Overwriting parts of a pointer; without knowing absolute addresses, the resulting state
|
|
/// cannot be represented by the CTFE interpreter.
|
|
OverwritePartialPointer(Pointer<AllocId>),
|
|
/// Attempting to read or copy parts of a pointer to somewhere else; without knowing absolute
|
|
/// addresses, the resulting state cannot be represented by the CTFE interpreter.
|
|
ReadPartialPointer(Pointer<AllocId>),
|
|
/// Encountered a pointer where we needed an integer.
|
|
ReadPointerAsInt(Option<(AllocId, BadBytesAccess)>),
|
|
/// Accessing thread local statics
|
|
ThreadLocalStatic(DefId),
|
|
/// Accessing an unsupported extern static.
|
|
ReadExternStatic(DefId),
|
|
}
|
|
|
|
/// Error information for when the program exhausted the resources granted to it
|
|
/// by the interpreter.
|
|
#[derive(Debug)]
|
|
pub enum ResourceExhaustionInfo {
|
|
/// The stack grew too big.
|
|
StackFrameLimitReached,
|
|
/// There is not enough memory (on the host) to perform an allocation.
|
|
MemoryExhausted,
|
|
/// The address space (of the target) is full.
|
|
AddressSpaceFull,
|
|
}
|
|
|
|
/// A trait for machine-specific errors (or other "machine stop" conditions).
|
|
pub trait MachineStopType: Any + fmt::Debug + Send {
|
|
/// The diagnostic message for this error
|
|
fn diagnostic_message(&self) -> DiagnosticMessage;
|
|
/// Add diagnostic arguments by passing name and value pairs to `adder`, which are passed to
|
|
/// fluent for formatting the translated diagnostic message.
|
|
fn add_args(
|
|
self: Box<Self>,
|
|
adder: &mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>),
|
|
);
|
|
}
|
|
|
|
impl dyn MachineStopType {
|
|
#[inline(always)]
|
|
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
|
|
let x: &dyn Any = self;
|
|
x.downcast_ref()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum InterpError<'tcx> {
|
|
/// The program caused undefined behavior.
|
|
UndefinedBehavior(UndefinedBehaviorInfo<'tcx>),
|
|
/// The program did something the interpreter does not support (some of these *might* be UB
|
|
/// but the interpreter is not sure).
|
|
Unsupported(UnsupportedOpInfo),
|
|
/// The program was invalid (ill-typed, bad MIR, not sufficiently monomorphized, ...).
|
|
InvalidProgram(InvalidProgramInfo<'tcx>),
|
|
/// The program exhausted the interpreter's resources (stack/heap too big,
|
|
/// execution takes too long, ...).
|
|
ResourceExhaustion(ResourceExhaustionInfo),
|
|
/// Stop execution for a machine-controlled reason. This is never raised by
|
|
/// the core engine itself.
|
|
MachineStop(Box<dyn MachineStopType>),
|
|
}
|
|
|
|
pub type InterpResult<'tcx, T = ()> = Result<T, InterpErrorInfo<'tcx>>;
|
|
|
|
impl InterpError<'_> {
|
|
/// Some errors do string formatting even if the error is never printed.
|
|
/// To avoid performance issues, there are places where we want to be sure to never raise these formatting errors,
|
|
/// so this method lets us detect them and `bug!` on unexpected errors.
|
|
pub fn formatted_string(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_))
|
|
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationError { .. })
|
|
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_))
|
|
)
|
|
}
|
|
}
|