diff --git a/src/librustc_middle/mir/interpret/error.rs b/src/librustc_middle/mir/interpret/error.rs index c904cd83a78bc..5be09c0e9bc2b 100644 --- a/src/librustc_middle/mir/interpret/error.rs +++ b/src/librustc_middle/mir/interpret/error.rs @@ -1,17 +1,13 @@ use super::{AllocId, Pointer, RawConst, Scalar}; use crate::mir::interpret::ConstValue; -use crate::ty::layout::LayoutError; -use crate::ty::query::TyCtxtAt; -use crate::ty::{self, layout, tls, FnSig, Ty}; +use crate::ty::{layout, query::TyCtxtAt, tls, FnSig, Ty}; use rustc_data_structures::sync::Lock; use rustc_errors::{pluralize, struct_span_err, DiagnosticBuilder, ErrorReported}; -use rustc_hir as hir; -use rustc_hir::definitions::DefPathData; use rustc_macros::HashStable; use rustc_session::CtfeBacktrace; -use rustc_span::{def_id::DefId, Pos, Span}; +use rustc_span::def_id::DefId; use rustc_target::abi::{Align, Size}; use std::{any::Any, backtrace::Backtrace, fmt, mem}; @@ -34,167 +30,6 @@ CloneTypeFoldableAndLiftImpls! { 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> { - pub instance: ty::Instance<'tcx>, - pub span: Span, - 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 closure")?; - } else { - write!(f, "inside `{}`", self.instance)?; - } - if !self.span.is_dummy() { - let lo = tcx.sess.source_map().lookup_char_pos(self.span.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<'_>), - ) -> ErrorHandled { - self.struct_generic(tcx, message, emit, None) - } - - pub fn report_as_error(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled { - self.struct_error(tcx, message, |mut e| e.emit()) - } - - pub fn report_as_lint( - &self, - tcx: TyCtxtAt<'tcx>, - message: &str, - lint_root: hir::HirId, - span: Option, - ) -> ErrorHandled { - 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), - ) - } - - /// 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, - ) -> ErrorHandled { - let must_error = match self.error { - err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => { - return ErrorHandled::TooGeneric; - } - err_inval!(TypeckError(error_reported)) => { - return ErrorHandled::Reported(error_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. Don't print a single-line backtrace though. - if self.stacktrace.len() > 1 { - for frame_info in &self.stacktrace { - err.span_label(frame_info.span, 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); - ErrorHandled::Reported(ErrorReported) - } else { - // Regular case. - if let Some(lint_root) = lint_root { - // Report as lint. - let hir_id = self - .stacktrace - .iter() - .rev() - .find_map(|frame| frame.lint_root) - .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)), - ); - ErrorHandled::Linted - } else { - // Report as hard error. - finish(struct_error(tcx, message), Some(err_msg)); - ErrorHandled::Reported(ErrorReported) - } - } - } -} - pub fn struct_error<'tcx>(tcx: TyCtxtAt<'tcx>, msg: &str) -> DiagnosticBuilder<'tcx> { struct_span_err!(tcx.sess, tcx.span, E0080, "{}", msg) } diff --git a/src/librustc_middle/mir/interpret/mod.rs b/src/librustc_middle/mir/interpret/mod.rs index 5e57b60894a58..2507f2184fff1 100644 --- a/src/librustc_middle/mir/interpret/mod.rs +++ b/src/librustc_middle/mir/interpret/mod.rs @@ -117,9 +117,9 @@ use crate::ty::subst::GenericArgKind; use crate::ty::{self, Instance, Ty, TyCtxt}; pub use self::error::{ - struct_error, CheckInAllocMsg, ConstEvalErr, ConstEvalRawResult, ConstEvalResult, ErrorHandled, - FrameInfo, InterpError, InterpErrorInfo, InterpResult, InvalidProgramInfo, MachineStopType, - ResourceExhaustionInfo, UndefinedBehaviorInfo, UninitBytesAccess, UnsupportedOpInfo, + struct_error, CheckInAllocMsg, ConstEvalRawResult, ConstEvalResult, ErrorHandled, InterpError, + InterpErrorInfo, InterpResult, InvalidProgramInfo, MachineStopType, ResourceExhaustionInfo, + UndefinedBehaviorInfo, UninitBytesAccess, UnsupportedOpInfo, }; pub use self::value::{get_slice_bytes, ConstValue, RawConst, Scalar, ScalarMaybeUninit}; diff --git a/src/librustc_mir/const_eval/error.rs b/src/librustc_mir/const_eval/error.rs index 8a72be33b8429..044d27a6a9dc3 100644 --- a/src/librustc_mir/const_eval/error.rs +++ b/src/librustc_mir/const_eval/error.rs @@ -1,12 +1,16 @@ use std::error::Error; use std::fmt; +use rustc_errors::{DiagnosticBuilder, ErrorReported}; +use rustc_hir as hir; use rustc_middle::mir::AssertKind; -use rustc_middle::ty::ConstInt; +use rustc_middle::ty::{layout::LayoutError, query::TyCtxtAt, ConstInt}; use rustc_span::{Span, Symbol}; use super::InterpCx; -use crate::interpret::{ConstEvalErr, InterpErrorInfo, Machine}; +use crate::interpret::{ + struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, +}; /// The CTFE machine has some custom error kinds. #[derive(Clone, Debug)] @@ -48,15 +52,155 @@ impl fmt::Display for ConstEvalErrKind { impl Error for ConstEvalErrKind {} -/// Turn an interpreter error into something to report to the user. -/// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace. -/// Should be called only if the error is actually going to to be reported! -pub fn error_to_const_error<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>>( - ecx: &InterpCx<'mir, 'tcx, M>, - error: InterpErrorInfo<'tcx>, - span: Option, -) -> ConstEvalErr<'tcx> { - error.print_backtrace(); - let stacktrace = ecx.generate_stacktrace(); - ConstEvalErr { error: error.kind, stacktrace, span: span.unwrap_or_else(|| ecx.cur_span()) } +/// When const-evaluation errors, this type is constructed with the resulting information, +/// and then used to emit the error as a lint or hard error. +#[derive(Debug)] +pub struct ConstEvalErr<'tcx> { + pub span: Span, + pub error: InterpError<'tcx>, + pub stacktrace: Vec>, +} + +impl<'tcx> ConstEvalErr<'tcx> { + /// Turn an interpreter error into something to report to the user. + /// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace. + /// Should be called only if the error is actually going to to be reported! + pub fn new<'mir, M: Machine<'mir, 'tcx>>( + ecx: &InterpCx<'mir, 'tcx, M>, + error: InterpErrorInfo<'tcx>, + span: Option, + ) -> ConstEvalErr<'tcx> + where + 'tcx: 'mir, + { + error.print_backtrace(); + let stacktrace = ecx.generate_stacktrace(); + ConstEvalErr { error: error.kind, stacktrace, span: span.unwrap_or_else(|| ecx.cur_span()) } + } + + pub fn struct_error( + &self, + tcx: TyCtxtAt<'tcx>, + message: &str, + emit: impl FnOnce(DiagnosticBuilder<'_>), + ) -> ErrorHandled { + self.struct_generic(tcx, message, emit, None) + } + + pub fn report_as_error(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled { + self.struct_error(tcx, message, |mut e| e.emit()) + } + + pub fn report_as_lint( + &self, + tcx: TyCtxtAt<'tcx>, + message: &str, + lint_root: hir::HirId, + span: Option, + ) -> ErrorHandled { + 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), + ) + } + + /// 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, + ) -> ErrorHandled { + let must_error = match self.error { + err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => { + return ErrorHandled::TooGeneric; + } + err_inval!(TypeckError(error_reported)) => { + return ErrorHandled::Reported(error_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. Don't print a single-line backtrace though. + if self.stacktrace.len() > 1 { + for frame_info in &self.stacktrace { + err.span_label(frame_info.span, 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); + ErrorHandled::Reported(ErrorReported) + } else { + // Regular case. + if let Some(lint_root) = lint_root { + // Report as lint. + let hir_id = self + .stacktrace + .iter() + .rev() + .find_map(|frame| frame.lint_root) + .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)), + ); + ErrorHandled::Linted + } else { + // Report as hard error. + finish(struct_error(tcx, message), Some(err_msg)); + ErrorHandled::Reported(ErrorReported) + } + } + } } diff --git a/src/librustc_mir/const_eval/eval_queries.rs b/src/librustc_mir/const_eval/eval_queries.rs index 42fba8982d23c..7fbe5c409d3ce 100644 --- a/src/librustc_mir/const_eval/eval_queries.rs +++ b/src/librustc_mir/const_eval/eval_queries.rs @@ -1,13 +1,14 @@ -use super::{error_to_const_error, CompileTimeEvalContext, CompileTimeInterpreter, MemoryExtra}; +use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr, MemoryExtra}; use crate::interpret::eval_nullary_intrinsic; use crate::interpret::{ intern_const_alloc_recursive, Allocation, ConstValue, GlobalId, Immediate, InternKind, InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, RawConst, RefTracking, Scalar, ScalarMaybeUninit, StackPopCleanup, }; + use rustc_hir::def::DefKind; use rustc_middle::mir; -use rustc_middle::mir::interpret::{ConstEvalErr, ErrorHandled}; +use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::traits::Reveal; use rustc_middle::ty::{self, subst::Subst, TyCtxt}; use rustc_span::source_map::Span; @@ -213,7 +214,7 @@ fn validate_and_turn_into_const<'tcx>( })(); val.map_err(|error| { - let err = error_to_const_error(&ecx, error, None); + let err = ConstEvalErr::new(&ecx, error, None); err.struct_error(ecx.tcx, "it is undefined behavior to use this value", |mut diag| { diag.note(note_on_undefined_behavior_error()); diag.emit(); @@ -312,7 +313,7 @@ pub fn const_eval_raw_provider<'tcx>( res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, &body)) .map(|place| RawConst { alloc_id: place.ptr.assert_ptr().alloc_id, ty: place.layout.ty }) .map_err(|error| { - let err = error_to_const_error(&ecx, error, None); + let err = ConstEvalErr::new(&ecx, error, None); // errors in statics are always emitted as fatal errors if is_static { // Ensure that if the above error was either `TooGeneric` or `Reported` diff --git a/src/librustc_mir/interpret/eval_context.rs b/src/librustc_mir/interpret/eval_context.rs index 1e9be09781508..fc192cd9b3cb2 100644 --- a/src/librustc_mir/interpret/eval_context.rs +++ b/src/librustc_mir/interpret/eval_context.rs @@ -1,22 +1,22 @@ use std::cell::Cell; +use std::fmt; use std::mem; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_hir::def::DefKind; -use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, def::DefKind, def_id::DefId, definitions::DefPathData}; use rustc_index::vec::IndexVec; use rustc_macros::HashStable; use rustc_middle::ich::StableHashingContext; use rustc_middle::mir; use rustc_middle::mir::interpret::{ - sign_extend, truncate, FrameInfo, GlobalId, InterpResult, Pointer, Scalar, + sign_extend, truncate, GlobalId, InterpResult, Pointer, Scalar, }; use rustc_middle::ty::layout::{self, TyAndLayout}; use rustc_middle::ty::{ self, query::TyCtxtAt, subst::SubstsRef, ParamEnv, Ty, TyCtxt, TypeFoldable, }; -use rustc_span::{source_map::DUMMY_SP, Span}; +use rustc_span::{source_map::DUMMY_SP, Pos, Span}; use rustc_target::abi::{Align, HasDataLayout, LayoutOf, Size, TargetDataLayout}; use super::{ @@ -88,6 +88,14 @@ pub struct Frame<'mir, 'tcx, Tag = (), Extra = ()> { pub loc: Option, } +/// What we store about a frame in an interpreter backtrace. +#[derive(Debug)] +pub struct FrameInfo<'tcx> { + pub instance: ty::Instance<'tcx>, + pub span: Span, + pub lint_root: Option, +} + #[derive(Clone, Eq, PartialEq, Debug, HashStable)] // Miri debug-prints these pub enum StackPopCleanup { /// Jump to the next block in the caller, or cause UB if None (that's a function @@ -185,6 +193,25 @@ impl<'mir, 'tcx, Tag, Extra> Frame<'mir, 'tcx, Tag, Extra> { } } +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 closure")?; + } else { + write!(f, "inside `{}`", self.instance)?; + } + if !self.span.is_dummy() { + let lo = tcx.sess.source_map().lookup_char_pos(self.span.lo()); + write!(f, " at {}:{}:{}", lo.file.name, lo.line, lo.col.to_usize() + 1)?; + } + Ok(()) + }) + } +} + impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for InterpCx<'mir, 'tcx, M> { #[inline] fn data_layout(&self) -> &TargetDataLayout { diff --git a/src/librustc_mir/interpret/mod.rs b/src/librustc_mir/interpret/mod.rs index ebb061f48518b..5218b03d65ecc 100644 --- a/src/librustc_mir/interpret/mod.rs +++ b/src/librustc_mir/interpret/mod.rs @@ -18,7 +18,7 @@ mod visitor; pub use rustc_middle::mir::interpret::*; // have all the `interpret` symbols in one place: here -pub use self::eval_context::{Frame, InterpCx, LocalState, LocalValue, StackPopCleanup}; +pub use self::eval_context::{Frame, FrameInfo, InterpCx, LocalState, LocalValue, StackPopCleanup}; pub use self::intern::{intern_const_alloc_recursive, InternKind}; pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump}; pub use self::memory::{get_static, AllocCheck, FnVal, Memory, MemoryKind}; diff --git a/src/librustc_mir/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs index db0b0415728ae..bff83b3c6db74 100644 --- a/src/librustc_mir/transform/const_prop.rs +++ b/src/librustc_mir/transform/const_prop.rs @@ -26,7 +26,7 @@ use rustc_span::{def_id::DefId, Span}; use rustc_target::abi::{HasDataLayout, LayoutOf, Size, TargetDataLayout}; use rustc_trait_selection::traits; -use crate::const_eval::error_to_const_error; +use crate::const_eval::ConstEvalErr; use crate::interpret::{ self, compile_time_machine, truncate, AllocId, Allocation, Frame, ImmTy, Immediate, InterpCx, LocalState, LocalValue, MemPlace, Memory, MemoryKind, OpTy, Operand as InterpOperand, PlaceTy, @@ -451,7 +451,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { Ok(op) => Some(op), Err(error) => { let tcx = self.ecx.tcx.at(c.span); - let err = error_to_const_error(&self.ecx, error, Some(c.span)); + let err = ConstEvalErr::new(&self.ecx, error, Some(c.span)); if let Some(lint_root) = self.lint_root(source_info) { let lint_only = match c.literal.val { // Promoteds must lint and not error as the user didn't ask for them