use super::{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; use crate::ty::{self, layout, Ty}; use backtrace::Backtrace; use rustc_errors::{struct_span_err, DiagnosticBuilder}; use rustc_hir as hir; use rustc_macros::HashStable; use rustc_span::{Pos, Span}; use rustc_target::spec::abi::Abi; use std::{any::Any, env, fmt}; #[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, RustcEncodable, RustcDecodable)] pub enum ErrorHandled { /// Already reported a lint or an error for this evaluation. Reported, /// Don't emit an error, the evaluation failed because the MIR was generic /// and the substs didn't fully monomorphize it. TooGeneric, } impl ErrorHandled { pub fn assert_reported(self) { match self { ErrorHandled::Reported => {} ErrorHandled::TooGeneric => bug!( "MIR interpretation failed without reporting an error \ even though it was fully monomorphized" ), } } } CloneTypeFoldableImpls! { ErrorHandled, } pub type ConstEvalRawResult<'tcx> = Result, ErrorHandled>; pub type ConstEvalResult<'tcx> = Result, ErrorHandled>; #[derive(Debug)] pub struct ConstEvalErr<'tcx> { pub span: Span, pub error: crate::mir::interpret::InterpError<'tcx>, pub stacktrace: Vec>, } #[derive(Debug)] pub struct FrameInfo<'tcx> { /// This span is in the caller. pub call_site: Span, pub instance: ty::Instance<'tcx>, pub lint_root: Option, } impl<'tcx> fmt::Display for FrameInfo<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ty::tls::with(|tcx| { if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr { write!(f, "inside call to closure")?; } else { write!(f, "inside call to `{}`", self.instance)?; } if !self.call_site.is_dummy() { let lo = tcx.sess.source_map().lookup_char_pos(self.call_site.lo()); write!(f, " at {}:{}:{}", lo.file.name, lo.line, lo.col.to_usize() + 1)?; } Ok(()) }) } } impl<'tcx> ConstEvalErr<'tcx> { pub fn struct_error( &self, tcx: TyCtxtAt<'tcx>, message: &str, emit: impl FnOnce(DiagnosticBuilder<'_>), ) -> Result<(), ErrorHandled> { self.struct_generic(tcx, message, emit, None) } pub fn report_as_error(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled { match self.struct_error(tcx, message, |mut e| e.emit()) { Ok(_) => ErrorHandled::Reported, Err(x) => x, } } pub fn report_as_lint( &self, tcx: TyCtxtAt<'tcx>, message: &str, lint_root: hir::HirId, span: Option, ) -> ErrorHandled { match self.struct_generic( tcx, message, |mut lint: DiagnosticBuilder<'_>| { // Apply the span. if let Some(span) = span { let primary_spans = lint.span.primary_spans().to_vec(); // point at the actual error as the primary span lint.replace_span_with(span); // point to the `const` statement as a secondary span // they don't have any label for sp in primary_spans { if sp != span { lint.span_label(sp, ""); } } } lint.emit(); }, Some(lint_root), ) { Ok(_) => ErrorHandled::Reported, Err(err) => err, } } /// Create a diagnostic for this const eval error. /// /// Sets the message passed in via `message` and adds span labels with detailed error /// information before handing control back to `emit` to do any final processing. /// It's the caller's responsibility to call emit(), stash(), etc. within the `emit` /// function to dispose of the diagnostic properly. /// /// If `lint_root.is_some()` report it as a lint, else report it as a hard error. /// (Except that for some errors, we ignore all that -- see `must_error` below.) fn struct_generic( &self, tcx: TyCtxtAt<'tcx>, message: &str, emit: impl FnOnce(DiagnosticBuilder<'_>), lint_root: Option, ) -> Result<(), ErrorHandled> { let must_error = match self.error { err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => { return Err(ErrorHandled::TooGeneric); } err_inval!(TypeckError) => return Err(ErrorHandled::Reported), // We must *always* hard error on these, even if the caller wants just a lint. err_inval!(Layout(LayoutError::SizeOverflow(_))) => true, _ => false, }; trace!("reporting const eval failure at {:?}", self.span); let err_msg = match &self.error { InterpError::MachineStop(msg) => { // A custom error (`ConstEvalErrKind` in `librustc_mir/interp/const_eval/error.rs`). // Should be turned into a string by now. msg.downcast_ref::().expect("invalid MachineStop payload").clone() } err => err.to_string(), }; let finish = |mut err: DiagnosticBuilder<'_>, span_msg: Option| { if let Some(span_msg) = span_msg { err.span_label(self.span, span_msg); } // Add spans for the stacktrace. // Skip the last, which is just the environment of the constant. The stacktrace // is sometimes empty because we create "fake" eval contexts in CTFE to do work // on constant values. if !self.stacktrace.is_empty() { for frame_info in &self.stacktrace[..self.stacktrace.len() - 1] { err.span_label(frame_info.call_site, frame_info.to_string()); } } // Let the caller finish the job. emit(err) }; if must_error { // The `message` makes little sense here, this is a more serious error than the // caller thinks anyway. // See . finish(struct_error(tcx, &err_msg), None); } else { // Regular case. if let Some(lint_root) = lint_root { // Report as lint. let hir_id = self .stacktrace .iter() .rev() .filter_map(|frame| frame.lint_root) .next() .unwrap_or(lint_root); tcx.struct_span_lint_hir( rustc_session::lint::builtin::CONST_ERR, hir_id, tcx.span, |lint| finish(lint.build(message), Some(err_msg)), ); } else { // Report as hard error. finish(struct_error(tcx, message), Some(err_msg)); } } Ok(()) } } pub fn struct_error<'tcx>(tcx: TyCtxtAt<'tcx>, msg: &str) -> DiagnosticBuilder<'tcx> { struct_span_err!(tcx.sess, tcx.span, E0080, "{}", msg) } /// Packages the kind of error we got from the const code interpreter /// up with a Rust-level backtrace of where the error occurred. /// Thsese should always be constructed by calling `.into()` on /// a `InterpError`. In `librustc_mir::interpret`, we have `throw_err_*` /// macros for this. #[derive(Debug)] pub struct InterpErrorInfo<'tcx> { pub kind: InterpError<'tcx>, backtrace: Option>, } impl fmt::Display for InterpErrorInfo<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.kind) } } impl InterpErrorInfo<'_> { pub fn print_backtrace(&mut self) { if let Some(ref mut backtrace) = self.backtrace { print_backtrace(&mut *backtrace); } } } fn print_backtrace(backtrace: &mut Backtrace) { backtrace.resolve(); eprintln!("\n\nAn error occurred in miri:\n{:?}", backtrace); } impl From for InterpErrorInfo<'_> { fn from(err: ErrorHandled) -> Self { match err { ErrorHandled::Reported => err_inval!(ReferencedConstant), ErrorHandled::TooGeneric => err_inval!(TooGeneric), } .into() } } impl<'tcx> From> for InterpErrorInfo<'tcx> { fn from(kind: InterpError<'tcx>) -> Self { let backtrace = match env::var("RUSTC_CTFE_BACKTRACE") { // Matching `RUST_BACKTRACE` -- we treat "0" the same as "not present". Ok(ref val) if val != "0" => { let mut backtrace = Backtrace::new_unresolved(); if val == "immediate" { // Print it now. print_backtrace(&mut backtrace); None } else { Some(Box::new(backtrace)) } } _ => None, }; InterpErrorInfo { kind, backtrace } } } /// 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. pub enum InvalidProgramInfo<'tcx> { /// Resolution can fail if we are in a too generic context. TooGeneric, /// Cannot compute this constant because it depends on another one /// which already produced an error. ReferencedConstant, /// Abort in case type errors are reached. TypeckError, /// An error occurred during layout computation. Layout(layout::LayoutError<'tcx>), } impl fmt::Debug for InvalidProgramInfo<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use InvalidProgramInfo::*; match self { TooGeneric => write!(f, "encountered overly generic constant"), ReferencedConstant => write!(f, "referenced constant has errors"), TypeckError => write!(f, "encountered constants with type errors, stopping evaluation"), Layout(ref err) => write!(f, "{}", err), } } } /// Error information for when the program caused Undefined Behavior. pub enum UndefinedBehaviorInfo { /// Free-form case. Only for errors that are never caught! Ub(String), /// Free-form case for experimental UB. Only for errors that are never caught! UbExperimental(String), /// Unreachable code was executed. Unreachable, /// An enum discriminant was set to a value which was outside the range of valid values. InvalidDiscriminant(ScalarMaybeUndef), /// 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, /// Overflowing inbounds pointer arithmetic. PointerArithOverflow, /// Invalid metadata in a wide pointer (using `str` to avoid allocations). InvalidMeta(&'static str), } impl fmt::Debug for UndefinedBehaviorInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use UndefinedBehaviorInfo::*; match self { Ub(msg) | UbExperimental(msg) => write!(f, "{}", msg), Unreachable => write!(f, "entering unreachable code"), InvalidDiscriminant(val) => write!(f, "encountering invalid enum discriminant {}", val), BoundsCheckFailed { ref len, ref index } => write!( f, "indexing out of bounds: the len is {:?} but the index is {:?}", len, index ), DivisionByZero => write!(f, "dividing by zero"), RemainderByZero => write!(f, "calculating the remainder with a divisor of zero"), PointerArithOverflow => write!(f, "overflowing in-bounds pointer arithmetic"), InvalidMeta(msg) => write!(f, "invalid metadata in wide pointer: {}", msg), } } } /// 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 casting pointers to "real" integers. /// /// Currently, we also use this as fall-back error kind for errors that have not been /// categorized yet. pub enum UnsupportedOpInfo<'tcx> { /// Free-form case. Only for errors that are never caught! Unsupported(String), /// When const-prop encounters a situation it does not support, it raises this error. /// 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, ModifiedStatic, TypeNotPrimitive(Ty<'tcx>), ReallocatedWrongMemoryKind(String, String), DeallocatedWrongMemoryKind(String, String), ReallocateNonBasePtr, DeallocateNonBasePtr, IncorrectAllocationInformation(Size, Size, Align, Align), HeapAllocZeroBytes, HeapAllocNonPowerOfTwoAlignment(u64), ReadFromReturnPointer, PathNotFound(Vec), TransmuteSizeDiff(Ty<'tcx>, Ty<'tcx>), } impl fmt::Debug for UnsupportedOpInfo<'tcx> { 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") } ModifiedConstantMemory => write!(f, "tried to modify constant memory"), 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!( 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) } } } } /// Error information for when the program exhausted the resources granted to it /// by the interpreter. pub enum ResourceExhaustionInfo { /// The stack grew too big. StackFrameLimitReached, /// The program ran into an infinite loop. InfiniteLoop, } impl fmt::Debug for ResourceExhaustionInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use ResourceExhaustionInfo::*; match self { StackFrameLimitReached => { write!(f, "reached the configured maximum number of stack frames") } InfiniteLoop => write!( f, "duplicate interpreter state observed here, const evaluation will never \ terminate" ), } } } pub enum InterpError<'tcx> { /// The program caused undefined behavior. 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>), /// 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), } pub type InterpResult<'tcx, T = ()> = Result>; impl fmt::Display for InterpError<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Forward `Display` to `Debug`. write!(f, "{:?}", self) } } impl fmt::Debug for InterpError<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use InterpError::*; match *self { Unsupported(ref msg) => write!(f, "{:?}", msg), InvalidProgram(ref msg) => write!(f, "{:?}", msg), UndefinedBehavior(ref msg) => write!(f, "{:?}", msg), ResourceExhaustion(ref msg) => write!(f, "{:?}", msg), MachineStop(_) => bug!("unhandled MachineStop"), } } } impl InterpError<'_> { /// Some errors allocate to be created as they contain free-form strings. /// And sometimes we want to be sure that did not happen as it is a /// waste of resources. pub fn allocates(&self) -> bool { match self { InterpError::MachineStop(_) | InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_)) | InterpError::Unsupported(UnsupportedOpInfo::ValidationFailure(_)) | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_)) | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::UbExperimental(_)) => true, _ => false, } } }