Use translatable diagnostics in rustc_const_eval
This commit is contained in:
parent
642c92e630
commit
4f83717cf7
93 changed files with 2375 additions and 1123 deletions
|
@ -1,17 +1,15 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
|
||||
use rustc_errors::Diagnostic;
|
||||
use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, IntoDiagnostic, IntoDiagnosticArg};
|
||||
use rustc_middle::mir::AssertKind;
|
||||
use rustc_middle::query::TyCtxtAt;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_middle::ty::{layout::LayoutError, ConstInt};
|
||||
use rustc_span::{Span, Symbol};
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::{ErrorGuaranteed, Span, Symbol};
|
||||
|
||||
use super::InterpCx;
|
||||
use crate::interpret::{
|
||||
struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType,
|
||||
UnsupportedOpInfo,
|
||||
};
|
||||
use crate::errors::{self, FrameNote, ReportErrorExt};
|
||||
use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, Machine, MachineStopType};
|
||||
|
||||
/// The CTFE machine has some custom error kinds.
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -23,7 +21,35 @@ pub enum ConstEvalErrKind {
|
|||
Abort(String),
|
||||
}
|
||||
|
||||
impl MachineStopType for ConstEvalErrKind {}
|
||||
impl MachineStopType for ConstEvalErrKind {
|
||||
fn diagnostic_message(&self) -> DiagnosticMessage {
|
||||
use crate::fluent_generated::*;
|
||||
use ConstEvalErrKind::*;
|
||||
match self {
|
||||
ConstAccessesStatic => const_eval_const_accesses_static,
|
||||
ModifiedGlobal => const_eval_modified_global,
|
||||
Panic { .. } => const_eval_panic,
|
||||
AssertFailure(x) => x.diagnostic_message(),
|
||||
Abort(msg) => msg.to_string().into(),
|
||||
}
|
||||
}
|
||||
fn add_args(
|
||||
self: Box<Self>,
|
||||
adder: &mut dyn FnMut(std::borrow::Cow<'static, str>, DiagnosticArgValue<'static>),
|
||||
) {
|
||||
use ConstEvalErrKind::*;
|
||||
match *self {
|
||||
ConstAccessesStatic | ModifiedGlobal | Abort(_) => {}
|
||||
AssertFailure(kind) => kind.add_args(adder),
|
||||
Panic { msg, line, col, file } => {
|
||||
adder("msg".into(), msg.into_diagnostic_arg());
|
||||
adder("file".into(), file.into_diagnostic_arg());
|
||||
adder("line".into(), line.into_diagnostic_arg());
|
||||
adder("col".into(), col.into_diagnostic_arg());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The errors become `MachineStop` with plain strings when being raised.
|
||||
// `ConstEvalErr` (in `librustc_middle/mir/interpret/error.rs`) knows to
|
||||
|
@ -34,151 +60,117 @@ impl<'tcx> Into<InterpErrorInfo<'tcx>> for ConstEvalErrKind {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConstEvalErrKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use self::ConstEvalErrKind::*;
|
||||
match self {
|
||||
ConstAccessesStatic => write!(f, "constant accesses static"),
|
||||
ModifiedGlobal => {
|
||||
write!(f, "modifying a static's initial value from another static's initializer")
|
||||
pub fn get_span_and_frames<'tcx, 'mir, M: Machine<'mir, 'tcx>>(
|
||||
ecx: &InterpCx<'mir, 'tcx, M>,
|
||||
) -> (Span, Vec<errors::FrameNote>)
|
||||
where
|
||||
'tcx: 'mir,
|
||||
{
|
||||
let mut stacktrace = ecx.generate_stacktrace();
|
||||
// Filter out `requires_caller_location` frames.
|
||||
stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
|
||||
let span = stacktrace.first().map(|f| f.span).unwrap_or(ecx.tcx.span);
|
||||
|
||||
let mut frames = Vec::new();
|
||||
|
||||
// Add notes to the backtrace. Don't print a single-line backtrace though.
|
||||
if stacktrace.len() > 1 {
|
||||
// Helper closure to print duplicated lines.
|
||||
let mut add_frame = |mut frame: errors::FrameNote| {
|
||||
frames.push(errors::FrameNote { times: 0, ..frame.clone() });
|
||||
// Don't print [... additional calls ...] if the number of lines is small
|
||||
if frame.times < 3 {
|
||||
let times = frame.times;
|
||||
frame.times = 0;
|
||||
frames.extend(std::iter::repeat(frame).take(times as usize));
|
||||
} else {
|
||||
frames.push(frame);
|
||||
}
|
||||
AssertFailure(msg) => write!(f, "{:?}", msg),
|
||||
Panic { msg, line, col, file } => {
|
||||
write!(f, "the evaluated program panicked at '{}', {}:{}:{}", msg, file, line, col)
|
||||
}
|
||||
Abort(msg) => write!(f, "{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
impl Error for ConstEvalErrKind {}
|
||||
|
||||
/// 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(super) struct ConstEvalErr<'tcx> {
|
||||
pub span: Span,
|
||||
pub error: InterpError<'tcx>,
|
||||
pub stacktrace: Vec<FrameInfo<'tcx>>,
|
||||
}
|
||||
|
||||
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 be reported!
|
||||
pub fn new<'mir, M: Machine<'mir, 'tcx>>(
|
||||
ecx: &InterpCx<'mir, 'tcx, M>,
|
||||
error: InterpErrorInfo<'tcx>,
|
||||
span: Option<Span>,
|
||||
) -> ConstEvalErr<'tcx>
|
||||
where
|
||||
'tcx: 'mir,
|
||||
{
|
||||
error.print_backtrace();
|
||||
let mut stacktrace = ecx.generate_stacktrace();
|
||||
// Filter out `requires_caller_location` frames.
|
||||
stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
|
||||
// If `span` is missing, use topmost remaining frame, or else the "root" span from `ecx.tcx`.
|
||||
let span = span.or_else(|| stacktrace.first().map(|f| f.span)).unwrap_or(ecx.tcx.span);
|
||||
ConstEvalErr { error: error.into_kind(), stacktrace, span }
|
||||
}
|
||||
|
||||
pub(super) fn report(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled {
|
||||
self.report_decorated(tcx, message, |_| {})
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip(self, decorate))]
|
||||
pub(super) fn decorate(&self, err: &mut Diagnostic, decorate: impl FnOnce(&mut Diagnostic)) {
|
||||
trace!("reporting const eval failure at {:?}", self.span);
|
||||
// Add some more context for select error types.
|
||||
match self.error {
|
||||
InterpError::Unsupported(
|
||||
UnsupportedOpInfo::ReadPointerAsBytes
|
||||
| UnsupportedOpInfo::PartialPointerOverwrite(_)
|
||||
| UnsupportedOpInfo::PartialPointerCopy(_),
|
||||
) => {
|
||||
err.help("this code performed an operation that depends on the underlying bytes representing a pointer");
|
||||
err.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Add spans for the stacktrace. Don't print a single-line backtrace though.
|
||||
if self.stacktrace.len() > 1 {
|
||||
// Helper closure to print duplicated lines.
|
||||
let mut flush_last_line = |last_frame: Option<(String, _)>, times| {
|
||||
if let Some((line, span)) = last_frame {
|
||||
err.span_note(span, line.clone());
|
||||
// Don't print [... additional calls ...] if the number of lines is small
|
||||
if times < 3 {
|
||||
for _ in 0..times {
|
||||
err.span_note(span, line.clone());
|
||||
}
|
||||
} else {
|
||||
err.span_note(
|
||||
span,
|
||||
format!("[... {} additional calls {} ...]", times, &line),
|
||||
);
|
||||
}
|
||||
let mut last_frame: Option<errors::FrameNote> = None;
|
||||
for frame_info in &stacktrace {
|
||||
let frame = frame_info.as_note(*ecx.tcx);
|
||||
match last_frame.as_mut() {
|
||||
Some(last_frame)
|
||||
if last_frame.span == frame.span
|
||||
&& last_frame.where_ == frame.where_
|
||||
&& last_frame.instance == frame.instance =>
|
||||
{
|
||||
last_frame.times += 1;
|
||||
}
|
||||
};
|
||||
|
||||
let mut last_frame = None;
|
||||
let mut times = 0;
|
||||
for frame_info in &self.stacktrace {
|
||||
let frame = (frame_info.to_string(), frame_info.span);
|
||||
if last_frame.as_ref() == Some(&frame) {
|
||||
times += 1;
|
||||
} else {
|
||||
flush_last_line(last_frame, times);
|
||||
Some(last_frame) => {
|
||||
add_frame(mem::replace(last_frame, frame));
|
||||
}
|
||||
None => {
|
||||
last_frame = Some(frame);
|
||||
times = 0;
|
||||
}
|
||||
}
|
||||
flush_last_line(last_frame, times);
|
||||
}
|
||||
// Let the caller attach any additional information it wants.
|
||||
decorate(err);
|
||||
if let Some(frame) = last_frame {
|
||||
add_frame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 `decorate` to do any final annotations,
|
||||
/// after which the diagnostic is emitted.
|
||||
///
|
||||
/// 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.)
|
||||
#[instrument(skip(self, tcx, decorate), level = "debug")]
|
||||
pub(super) fn report_decorated(
|
||||
&self,
|
||||
tcx: TyCtxtAt<'tcx>,
|
||||
message: &str,
|
||||
decorate: impl FnOnce(&mut Diagnostic),
|
||||
) -> ErrorHandled {
|
||||
debug!("self.error: {:?}", self.error);
|
||||
// Special handling for certain errors
|
||||
match &self.error {
|
||||
// Don't emit a new diagnostic for these errors
|
||||
err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
|
||||
ErrorHandled::TooGeneric
|
||||
}
|
||||
err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(*error_reported),
|
||||
err_inval!(Layout(LayoutError::SizeOverflow(_))) => {
|
||||
// We must *always* hard error on these, even if the caller wants just a lint.
|
||||
// The `message` makes little sense here, this is a more serious error than the
|
||||
// caller thinks anyway.
|
||||
// See <https://github.com/rust-lang/rust/pull/63152>.
|
||||
let mut err = struct_error(tcx, &self.error.to_string());
|
||||
self.decorate(&mut err, decorate);
|
||||
ErrorHandled::Reported(err.emit().into())
|
||||
}
|
||||
_ => {
|
||||
// Report as hard error.
|
||||
let mut err = struct_error(tcx, message);
|
||||
err.span_label(self.span, self.error.to_string());
|
||||
self.decorate(&mut err, decorate);
|
||||
ErrorHandled::Reported(err.emit().into())
|
||||
(span, frames)
|
||||
}
|
||||
|
||||
/// Create a diagnostic for a const eval error.
|
||||
///
|
||||
/// This will use the `mk` function for creating the error which will get passed labels according to
|
||||
/// the `InterpError` and the span and a stacktrace of current execution according to
|
||||
/// `get_span_and_frames`.
|
||||
pub(super) fn report<'tcx, C, F, E>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
error: InterpError<'tcx>,
|
||||
span: Option<Span>,
|
||||
get_span_and_frames: C,
|
||||
mk: F,
|
||||
) -> ErrorHandled
|
||||
where
|
||||
C: FnOnce() -> (Span, Vec<FrameNote>),
|
||||
F: FnOnce(Span, Vec<FrameNote>) -> E,
|
||||
E: IntoDiagnostic<'tcx, ErrorGuaranteed>,
|
||||
{
|
||||
// Special handling for certain errors
|
||||
match error {
|
||||
// Don't emit a new diagnostic for these errors
|
||||
err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => {
|
||||
ErrorHandled::TooGeneric
|
||||
}
|
||||
err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(error_reported),
|
||||
err_inval!(Layout(layout_error @ LayoutError::SizeOverflow(_))) => {
|
||||
// We must *always* hard error on these, even if the caller wants just a lint.
|
||||
// The `message` makes little sense here, this is a more serious error than the
|
||||
// caller thinks anyway.
|
||||
// See <https://github.com/rust-lang/rust/pull/63152>.
|
||||
let (our_span, frames) = get_span_and_frames();
|
||||
let span = span.unwrap_or(our_span);
|
||||
let mut err =
|
||||
tcx.sess.create_err(Spanned { span, node: layout_error.into_diagnostic() });
|
||||
err.code(rustc_errors::error_code!(E0080));
|
||||
let Some((mut err, handler)) = err.into_diagnostic() else {
|
||||
panic!("did not emit diag");
|
||||
};
|
||||
for frame in frames {
|
||||
err.eager_subdiagnostic(handler, frame);
|
||||
}
|
||||
|
||||
ErrorHandled::Reported(handler.emit_diagnostic(&mut err).unwrap().into())
|
||||
}
|
||||
_ => {
|
||||
// Report as hard error.
|
||||
let (our_span, frames) = get_span_and_frames();
|
||||
let span = span.unwrap_or(our_span);
|
||||
let err = mk(span, frames);
|
||||
let mut err = tcx.sess.create_err(err);
|
||||
|
||||
let msg = error.diagnostic_message();
|
||||
error.add_args(&tcx.sess.parse_sess.span_diagnostic, &mut err);
|
||||
|
||||
// Use *our* span to label the interp error
|
||||
err.span_label(our_span, msg);
|
||||
ErrorHandled::Reported(err.emit().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use crate::const_eval::CheckAlignment;
|
||||
use std::borrow::Cow;
|
||||
use crate::errors::ConstEvalError;
|
||||
|
||||
use either::{Left, Right};
|
||||
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_middle::mir;
|
||||
use rustc_middle::mir::interpret::ErrorHandled;
|
||||
use rustc_middle::mir::pretty::display_allocation;
|
||||
use rustc_middle::mir::interpret::{ErrorHandled, InterpErrorInfo};
|
||||
use rustc_middle::mir::pretty::write_allocation_bytes;
|
||||
use rustc_middle::traits::Reveal;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::print::with_no_trimmed_paths;
|
||||
|
@ -14,7 +14,8 @@ use rustc_middle::ty::{self, TyCtxt};
|
|||
use rustc_span::source_map::Span;
|
||||
use rustc_target::abi::{self, Abi};
|
||||
|
||||
use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr};
|
||||
use super::{CompileTimeEvalContext, CompileTimeInterpreter};
|
||||
use crate::errors;
|
||||
use crate::interpret::eval_nullary_intrinsic;
|
||||
use crate::interpret::{
|
||||
intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId,
|
||||
|
@ -22,10 +23,6 @@ use crate::interpret::{
|
|||
RefTracking, StackPopCleanup,
|
||||
};
|
||||
|
||||
const NOTE_ON_UNDEFINED_BEHAVIOR_ERROR: &str = "The rules on what exactly is undefined behavior aren't clear, \
|
||||
so this check might be overzealous. Please open an issue on the rustc \
|
||||
repository if you believe it should not be considered undefined behavior.";
|
||||
|
||||
// Returns a pointer to where the result lives
|
||||
fn eval_body_using_ecx<'mir, 'tcx>(
|
||||
ecx: &mut CompileTimeEvalContext<'mir, 'tcx>,
|
||||
|
@ -253,8 +250,14 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
|
|||
};
|
||||
return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| {
|
||||
let span = tcx.def_span(def_id);
|
||||
let error = ConstEvalErr { error: error.into_kind(), stacktrace: vec![], span };
|
||||
error.report(tcx.at(span), "could not evaluate nullary intrinsic")
|
||||
|
||||
super::report(
|
||||
tcx,
|
||||
error.into_kind(),
|
||||
Some(span),
|
||||
|| (span, vec![]),
|
||||
|span, _| errors::NullaryIntrinsicError { span },
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -318,9 +321,11 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
|
|||
let res = ecx.load_mir(cid.instance.def, cid.promoted);
|
||||
match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, &body)) {
|
||||
Err(error) => {
|
||||
let err = ConstEvalErr::new(&ecx, error, None);
|
||||
let msg = if is_static {
|
||||
Cow::from("could not evaluate static initializer")
|
||||
let (error, backtrace) = error.into_parts();
|
||||
backtrace.print_backtrace();
|
||||
|
||||
let (kind, instance) = if is_static {
|
||||
("static", String::new())
|
||||
} else {
|
||||
// If the current item has generics, we'd like to enrich the message with the
|
||||
// instance and its substs: to show the actual compile-time values, in addition to
|
||||
|
@ -328,19 +333,29 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
|
|||
let instance = &key.value.instance;
|
||||
if !instance.substs.is_empty() {
|
||||
let instance = with_no_trimmed_paths!(instance.to_string());
|
||||
let msg = format!("evaluation of `{}` failed", instance);
|
||||
Cow::from(msg)
|
||||
("const_with_path", instance)
|
||||
} else {
|
||||
Cow::from("evaluation of constant value failed")
|
||||
("const", String::new())
|
||||
}
|
||||
};
|
||||
|
||||
Err(err.report(ecx.tcx.at(err.span), &msg))
|
||||
Err(super::report(
|
||||
*ecx.tcx,
|
||||
error,
|
||||
None,
|
||||
|| super::get_span_and_frames(&ecx),
|
||||
|span, frames| ConstEvalError {
|
||||
span,
|
||||
error_kind: kind,
|
||||
instance,
|
||||
frame_notes: frames,
|
||||
},
|
||||
))
|
||||
}
|
||||
Ok(mplace) => {
|
||||
// Since evaluation had no errors, validate the resulting constant.
|
||||
// This is a separate `try` block to provide more targeted error reporting.
|
||||
let validation = try {
|
||||
let validation: Result<_, InterpErrorInfo<'_>> = try {
|
||||
let mut ref_tracking = RefTracking::new(mplace);
|
||||
let mut inner = false;
|
||||
while let Some((mplace, path)) = ref_tracking.todo.pop() {
|
||||
|
@ -357,23 +372,37 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
|
|||
}
|
||||
};
|
||||
let alloc_id = mplace.ptr.provenance.unwrap();
|
||||
|
||||
// Validation failed, report an error. This is always a hard error.
|
||||
if let Err(error) = validation {
|
||||
// Validation failed, report an error. This is always a hard error.
|
||||
let err = ConstEvalErr::new(&ecx, error, None);
|
||||
Err(err.report_decorated(
|
||||
ecx.tcx,
|
||||
"it is undefined behavior to use this value",
|
||||
|diag| {
|
||||
if matches!(err.error, InterpError::UndefinedBehavior(_)) {
|
||||
diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR);
|
||||
}
|
||||
diag.note(format!(
|
||||
"the raw bytes of the constant ({}",
|
||||
display_allocation(
|
||||
*ecx.tcx,
|
||||
ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner()
|
||||
)
|
||||
));
|
||||
let (error, backtrace) = error.into_parts();
|
||||
backtrace.print_backtrace();
|
||||
|
||||
let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {});
|
||||
|
||||
let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
|
||||
let mut bytes = String::new();
|
||||
if alloc.size() != abi::Size::ZERO {
|
||||
bytes = "\n".into();
|
||||
// FIXME(translation) there might be pieces that are translatable.
|
||||
write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, " ").unwrap();
|
||||
}
|
||||
let raw_bytes = errors::RawBytesNote {
|
||||
size: alloc.size().bytes(),
|
||||
align: alloc.align.bytes(),
|
||||
bytes,
|
||||
};
|
||||
|
||||
Err(super::report(
|
||||
*ecx.tcx,
|
||||
error,
|
||||
None,
|
||||
|| super::get_span_and_frames(&ecx),
|
||||
move |span, frames| errors::UndefinedBehavior {
|
||||
span,
|
||||
ub_note,
|
||||
frames,
|
||||
raw_bytes,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
|
|
|
@ -25,6 +25,7 @@ use crate::interpret::{
|
|||
self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
|
||||
InterpResult, OpTy, PlaceTy, Pointer, Scalar,
|
||||
};
|
||||
use crate::{errors, fluent_generated as fluent};
|
||||
|
||||
use super::error::*;
|
||||
|
||||
|
@ -254,7 +255,10 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
|
|||
let target_align = self.read_scalar(&args[1])?.to_target_usize(self)?;
|
||||
|
||||
if !target_align.is_power_of_two() {
|
||||
throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_align_offset_invalid_align,
|
||||
target_align = target_align,
|
||||
);
|
||||
}
|
||||
|
||||
match self.ptr_try_get_alloc_id(ptr) {
|
||||
|
@ -360,15 +364,18 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
|||
"`alignment_check_failed` called when no alignment check requested"
|
||||
),
|
||||
CheckAlignment::FutureIncompat => {
|
||||
let err = ConstEvalErr::new(ecx, err, None);
|
||||
ecx.tcx.struct_span_lint_hir(
|
||||
let (_, backtrace) = err.into_parts();
|
||||
backtrace.print_backtrace();
|
||||
let (span, frames) = super::get_span_and_frames(&ecx);
|
||||
|
||||
ecx.tcx.emit_spanned_lint(
|
||||
INVALID_ALIGNMENT,
|
||||
ecx.stack().iter().find_map(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID),
|
||||
err.span,
|
||||
err.error.to_string(),
|
||||
|db| {
|
||||
err.decorate(db, |_| {});
|
||||
db
|
||||
span,
|
||||
errors::AlignmentCheckFailed {
|
||||
has: has.bytes(),
|
||||
required: required.bytes(),
|
||||
frames,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
|
@ -482,7 +489,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
|||
|
||||
let align = match Align::from_bytes(align) {
|
||||
Ok(a) => a,
|
||||
Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
|
||||
Err(err) => throw_ub_custom!(
|
||||
fluent::const_eval_invalid_align_details,
|
||||
name = "const_allocate",
|
||||
err_kind = err.diag_ident(),
|
||||
align = err.align()
|
||||
),
|
||||
};
|
||||
|
||||
let ptr = ecx.allocate_ptr(
|
||||
|
@ -500,7 +512,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
|||
let size = Size::from_bytes(size);
|
||||
let align = match Align::from_bytes(align) {
|
||||
Ok(a) => a,
|
||||
Err(err) => throw_ub_format!("align has to be a power of 2, {}", err),
|
||||
Err(err) => throw_ub_custom!(
|
||||
fluent::const_eval_invalid_align_details,
|
||||
name = "const_deallocate",
|
||||
err_kind = err.diag_ident(),
|
||||
align = err.align()
|
||||
),
|
||||
};
|
||||
|
||||
// If an allocation is created in an another const,
|
||||
|
|
|
@ -73,17 +73,8 @@ pub(crate) fn eval_to_valtree<'tcx>(
|
|||
let global_const_id = cid.display(tcx);
|
||||
match err {
|
||||
ValTreeCreationError::NodesOverflow => {
|
||||
let msg = format!(
|
||||
"maximum number of nodes exceeded in constant {}",
|
||||
&global_const_id
|
||||
);
|
||||
let mut diag = match tcx.hir().span_if_local(did) {
|
||||
Some(span) => {
|
||||
tcx.sess.create_err(MaxNumNodesInConstErr { span, global_const_id })
|
||||
}
|
||||
None => tcx.sess.struct_err(msg),
|
||||
};
|
||||
diag.emit();
|
||||
let span = tcx.hir().span_if_local(did);
|
||||
tcx.sess.emit_err(MaxNumNodesInConstErr { span, global_const_id });
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
use rustc_errors::{
|
||||
DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler,
|
||||
IntoDiagnostic,
|
||||
};
|
||||
use rustc_hir::ConstContext;
|
||||
use rustc_macros::{Diagnostic, LintDiagnostic};
|
||||
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
|
||||
use rustc_middle::mir::interpret::{
|
||||
CheckInAllocMsg, ExpectedKind, InterpError, InvalidMetaKind, InvalidProgramInfo, PointerKind,
|
||||
ResourceExhaustionInfo, UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo,
|
||||
};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::Span;
|
||||
use rustc_target::abi::call::AdjustForForeignAbiError;
|
||||
use rustc_target::abi::{Size, WrappingRange};
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_dangling_ptr_in_final)]
|
||||
pub(crate) struct DanglingPtrInFinal {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_unstable_in_stable)]
|
||||
|
@ -92,7 +110,7 @@ pub(crate) struct TransientMutBorrowErrRaw {
|
|||
#[diag(const_eval_max_num_nodes_in_const)]
|
||||
pub(crate) struct MaxNumNodesInConstErr {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub span: Option<Span>,
|
||||
pub global_const_id: String,
|
||||
}
|
||||
|
||||
|
@ -175,6 +193,14 @@ pub(crate) struct UnallowedInlineAsm {
|
|||
pub kind: ConstContext,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_unsupported_untyped_pointer)]
|
||||
#[note]
|
||||
pub(crate) struct UnsupportedUntypedPointer {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_interior_mutable_data_refer, code = "E0492")]
|
||||
pub(crate) struct InteriorMutableDataRefer {
|
||||
|
@ -212,3 +238,616 @@ pub struct LongRunningWarn {
|
|||
#[help]
|
||||
pub item_span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_erroneous_constant)]
|
||||
pub(crate) struct ErroneousConstUsed {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
#[note(const_eval_non_const_impl)]
|
||||
pub(crate) struct NonConstImplNote {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic, PartialEq, Eq, Clone)]
|
||||
#[note(const_eval_frame_note)]
|
||||
pub struct FrameNote {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub times: i32,
|
||||
pub where_: &'static str,
|
||||
pub instance: String,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
#[note(const_eval_raw_bytes)]
|
||||
pub struct RawBytesNote {
|
||||
pub size: u64,
|
||||
pub align: u64,
|
||||
pub bytes: String,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_for_loop_into_iter_non_const, code = "E0015")]
|
||||
pub struct NonConstForLoopIntoIter<'tcx> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub ty: Ty<'tcx>,
|
||||
pub kind: ConstContext,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_question_branch_non_const, code = "E0015")]
|
||||
pub struct NonConstQuestionBranch<'tcx> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub ty: Ty<'tcx>,
|
||||
pub kind: ConstContext,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_question_from_residual_non_const, code = "E0015")]
|
||||
pub struct NonConstQuestionFromResidual<'tcx> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub ty: Ty<'tcx>,
|
||||
pub kind: ConstContext,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_try_block_from_output_non_const, code = "E0015")]
|
||||
pub struct NonConstTryBlockFromOutput<'tcx> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub ty: Ty<'tcx>,
|
||||
pub kind: ConstContext,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_await_non_const, code = "E0015")]
|
||||
pub struct NonConstAwait<'tcx> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub ty: Ty<'tcx>,
|
||||
pub kind: ConstContext,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_closure_non_const, code = "E0015")]
|
||||
pub struct NonConstClosure {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub kind: ConstContext,
|
||||
#[subdiagnostic]
|
||||
pub note: Option<NonConstClosureNote>,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
pub enum NonConstClosureNote {
|
||||
#[note(const_eval_closure_fndef_not_const)]
|
||||
FnDef {
|
||||
#[primary_span]
|
||||
span: Span,
|
||||
},
|
||||
#[note(const_eval_fn_ptr_call)]
|
||||
FnPtr,
|
||||
#[note(const_eval_closure_call)]
|
||||
Closure,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
#[multipart_suggestion(const_eval_consider_dereferencing, applicability = "machine-applicable")]
|
||||
pub struct ConsiderDereferencing {
|
||||
pub deref: String,
|
||||
#[suggestion_part(code = "{deref}")]
|
||||
pub span: Span,
|
||||
#[suggestion_part(code = "{deref}")]
|
||||
pub rhs_span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_operator_non_const, code = "E0015")]
|
||||
pub struct NonConstOperator {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub kind: ConstContext,
|
||||
#[subdiagnostic]
|
||||
pub sugg: Option<ConsiderDereferencing>,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_deref_coercion_non_const, code = "E0015")]
|
||||
#[note]
|
||||
pub struct NonConstDerefCoercion<'tcx> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub ty: Ty<'tcx>,
|
||||
pub kind: ConstContext,
|
||||
pub target_ty: Ty<'tcx>,
|
||||
#[note(const_eval_target_note)]
|
||||
pub deref_target: Option<Span>,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_live_drop, code = "E0493")]
|
||||
pub struct LiveDrop<'tcx> {
|
||||
#[primary_span]
|
||||
#[label]
|
||||
pub span: Span,
|
||||
pub kind: ConstContext,
|
||||
pub dropped_ty: Ty<'tcx>,
|
||||
#[label(const_eval_dropped_at_label)]
|
||||
pub dropped_at: Option<Span>,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(const_eval_align_check_failed)]
|
||||
pub struct AlignmentCheckFailed {
|
||||
pub has: u64,
|
||||
pub required: u64,
|
||||
#[subdiagnostic]
|
||||
pub frames: Vec<FrameNote>,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_error, code = "E0080")]
|
||||
pub struct ConstEvalError {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
/// One of "const", "const_with_path", and "static"
|
||||
pub error_kind: &'static str,
|
||||
pub instance: String,
|
||||
#[subdiagnostic]
|
||||
pub frame_notes: Vec<FrameNote>,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_nullary_intrinsic_fail)]
|
||||
pub struct NullaryIntrinsicError {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(const_eval_undefined_behavior, code = "E0080")]
|
||||
pub struct UndefinedBehavior {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
#[note(const_eval_undefined_behavior_note)]
|
||||
pub ub_note: Option<()>,
|
||||
#[subdiagnostic]
|
||||
pub frames: Vec<FrameNote>,
|
||||
#[subdiagnostic]
|
||||
pub raw_bytes: RawBytesNote,
|
||||
}
|
||||
|
||||
pub trait ReportErrorExt {
|
||||
/// Returns the diagnostic message for this error.
|
||||
fn diagnostic_message(&self) -> DiagnosticMessage;
|
||||
fn add_args<G: EmissionGuarantee>(
|
||||
self,
|
||||
handler: &Handler,
|
||||
builder: &mut DiagnosticBuilder<'_, G>,
|
||||
);
|
||||
}
|
||||
|
||||
fn bad_pointer_message(msg: CheckInAllocMsg, handler: &Handler) -> String {
|
||||
use crate::fluent_generated::*;
|
||||
|
||||
let msg = match msg {
|
||||
CheckInAllocMsg::DerefTest => const_eval_deref_test,
|
||||
CheckInAllocMsg::MemoryAccessTest => const_eval_memory_access_test,
|
||||
CheckInAllocMsg::PointerArithmeticTest => const_eval_pointer_arithmetic_test,
|
||||
CheckInAllocMsg::OffsetFromTest => const_eval_offset_from_test,
|
||||
CheckInAllocMsg::InboundsTest => const_eval_in_bounds_test,
|
||||
};
|
||||
|
||||
handler.eagerly_translate_to_string(msg, [].into_iter())
|
||||
}
|
||||
|
||||
impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
|
||||
fn diagnostic_message(&self) -> DiagnosticMessage {
|
||||
use crate::fluent_generated::*;
|
||||
use UndefinedBehaviorInfo::*;
|
||||
match self {
|
||||
Ub(msg) => (&**msg).into(),
|
||||
Unreachable => const_eval_unreachable,
|
||||
BoundsCheckFailed { .. } => const_eval_bounds_check_failed,
|
||||
DivisionByZero => const_eval_division_by_zero,
|
||||
RemainderByZero => const_eval_remainder_by_zero,
|
||||
DivisionOverflow => const_eval_division_overflow,
|
||||
RemainderOverflow => const_eval_remainder_overflow,
|
||||
PointerArithOverflow => const_eval_pointer_arithmetic_overflow,
|
||||
InvalidMeta(InvalidMetaKind::SliceTooBig) => const_eval_invalid_meta_slice,
|
||||
InvalidMeta(InvalidMetaKind::TooBig) => const_eval_invalid_meta,
|
||||
UnterminatedCString(_) => const_eval_unterminated_c_string,
|
||||
PointerUseAfterFree(_) => const_eval_pointer_use_after_free,
|
||||
PointerOutOfBounds { ptr_size: Size::ZERO, .. } => const_eval_zst_pointer_out_of_bounds,
|
||||
PointerOutOfBounds { .. } => const_eval_pointer_out_of_bounds,
|
||||
DanglingIntPointer(0, _) => const_eval_dangling_null_pointer,
|
||||
DanglingIntPointer(_, _) => const_eval_dangling_int_pointer,
|
||||
AlignmentCheckFailed { .. } => const_eval_alignment_check_failed,
|
||||
WriteToReadOnly(_) => const_eval_write_to_read_only,
|
||||
DerefFunctionPointer(_) => const_eval_deref_function_pointer,
|
||||
DerefVTablePointer(_) => const_eval_deref_vtable_pointer,
|
||||
InvalidBool(_) => const_eval_invalid_bool,
|
||||
InvalidChar(_) => const_eval_invalid_char,
|
||||
InvalidTag(_) => const_eval_invalid_tag,
|
||||
InvalidFunctionPointer(_) => const_eval_invalid_function_pointer,
|
||||
InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer,
|
||||
InvalidStr(_) => const_eval_invalid_str,
|
||||
InvalidUninitBytes(None) => const_eval_invalid_uninit_bytes_unknown,
|
||||
InvalidUninitBytes(Some(_)) => const_eval_invalid_uninit_bytes,
|
||||
DeadLocal => const_eval_dead_local,
|
||||
ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch,
|
||||
UninhabitedEnumVariantWritten => const_eval_uninhabited_enum_variant_written,
|
||||
Validation(e) => e.diagnostic_message(),
|
||||
Custom(x) => (x.msg)(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_args<G: EmissionGuarantee>(
|
||||
self,
|
||||
handler: &Handler,
|
||||
builder: &mut DiagnosticBuilder<'_, G>,
|
||||
) {
|
||||
use UndefinedBehaviorInfo::*;
|
||||
match self {
|
||||
Ub(_)
|
||||
| Unreachable
|
||||
| DivisionByZero
|
||||
| RemainderByZero
|
||||
| DivisionOverflow
|
||||
| RemainderOverflow
|
||||
| PointerArithOverflow
|
||||
| InvalidMeta(InvalidMetaKind::SliceTooBig)
|
||||
| InvalidMeta(InvalidMetaKind::TooBig)
|
||||
| InvalidUninitBytes(None)
|
||||
| DeadLocal
|
||||
| UninhabitedEnumVariantWritten => {}
|
||||
BoundsCheckFailed { len, index } => {
|
||||
builder.set_arg("len", len);
|
||||
builder.set_arg("index", index);
|
||||
}
|
||||
UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
|
||||
builder.set_arg("pointer", ptr);
|
||||
}
|
||||
PointerUseAfterFree(allocation) => {
|
||||
builder.set_arg("allocation", allocation);
|
||||
}
|
||||
PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => {
|
||||
builder
|
||||
.set_arg("alloc_id", alloc_id)
|
||||
.set_arg("alloc_size", alloc_size.bytes())
|
||||
.set_arg("ptr_offset", ptr_offset)
|
||||
.set_arg("ptr_size", ptr_size.bytes())
|
||||
.set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
|
||||
}
|
||||
DanglingIntPointer(ptr, msg) => {
|
||||
if ptr != 0 {
|
||||
builder.set_arg("pointer", format!("{ptr:#x}[noalloc]"));
|
||||
}
|
||||
|
||||
builder.set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
|
||||
}
|
||||
AlignmentCheckFailed { required, has } => {
|
||||
builder.set_arg("required", required.bytes());
|
||||
builder.set_arg("has", has.bytes());
|
||||
}
|
||||
WriteToReadOnly(alloc) | DerefFunctionPointer(alloc) | DerefVTablePointer(alloc) => {
|
||||
builder.set_arg("allocation", alloc);
|
||||
}
|
||||
InvalidBool(b) => {
|
||||
builder.set_arg("value", format!("{b:02x}"));
|
||||
}
|
||||
InvalidChar(c) => {
|
||||
builder.set_arg("value", format!("{c:08x}"));
|
||||
}
|
||||
InvalidTag(tag) => {
|
||||
builder.set_arg("tag", format!("{tag:x}"));
|
||||
}
|
||||
InvalidStr(err) => {
|
||||
builder.set_arg("err", format!("{err}"));
|
||||
}
|
||||
InvalidUninitBytes(Some((alloc, info))) => {
|
||||
builder.set_arg("alloc", alloc);
|
||||
builder.set_arg("access", info.access);
|
||||
builder.set_arg("uninit", info.uninit);
|
||||
}
|
||||
ScalarSizeMismatch(info) => {
|
||||
builder.set_arg("target_size", info.target_size);
|
||||
builder.set_arg("data_size", info.data_size);
|
||||
}
|
||||
Validation(e) => e.add_args(handler, builder),
|
||||
Custom(custom) => {
|
||||
(custom.add_args)(&mut |name, value| {
|
||||
builder.set_arg(name, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
|
||||
fn diagnostic_message(&self) -> DiagnosticMessage {
|
||||
use crate::fluent_generated::*;
|
||||
use rustc_middle::mir::interpret::ValidationErrorKind::*;
|
||||
match self.kind {
|
||||
PtrToUninhabited { ptr_kind: PointerKind::Box, .. } => const_eval_box_to_uninhabited,
|
||||
PtrToUninhabited { ptr_kind: PointerKind::Ref, .. } => const_eval_ref_to_uninhabited,
|
||||
|
||||
PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_box_to_static,
|
||||
PtrToStatic { ptr_kind: PointerKind::Ref } => const_eval_ref_to_static,
|
||||
|
||||
PtrToMut { ptr_kind: PointerKind::Box } => const_eval_box_to_mut,
|
||||
PtrToMut { ptr_kind: PointerKind::Ref } => const_eval_ref_to_mut,
|
||||
|
||||
ExpectedNonPtr { .. } => const_eval_expected_non_ptr,
|
||||
MutableRefInConst => const_eval_mutable_ref_in_const,
|
||||
NullFnPtr => const_eval_null_fn_ptr,
|
||||
NeverVal => const_eval_never_val,
|
||||
NullablePtrOutOfRange { .. } => const_eval_nullable_ptr_out_of_range,
|
||||
PtrOutOfRange { .. } => const_eval_ptr_out_of_range,
|
||||
OutOfRange { .. } => const_eval_out_of_range,
|
||||
UnsafeCell => const_eval_unsafe_cell,
|
||||
UninhabitedVal { .. } => const_eval_uninhabited_val,
|
||||
InvalidEnumTag { .. } => const_eval_invalid_enum_tag,
|
||||
UninitEnumTag => const_eval_uninit_enum_tag,
|
||||
UninitStr => const_eval_uninit_str,
|
||||
Uninit { expected: ExpectedKind::Bool } => const_eval_uninit_bool,
|
||||
Uninit { expected: ExpectedKind::Reference } => const_eval_uninit_ref,
|
||||
Uninit { expected: ExpectedKind::Box } => const_eval_uninit_box,
|
||||
Uninit { expected: ExpectedKind::RawPtr } => const_eval_uninit_raw_ptr,
|
||||
Uninit { expected: ExpectedKind::InitScalar } => const_eval_uninit_init_scalar,
|
||||
Uninit { expected: ExpectedKind::Char } => const_eval_uninit_char,
|
||||
Uninit { expected: ExpectedKind::Float } => const_eval_uninit_float,
|
||||
Uninit { expected: ExpectedKind::Int } => const_eval_uninit_int,
|
||||
Uninit { expected: ExpectedKind::FnPtr } => const_eval_uninit_fn_ptr,
|
||||
UninitVal => const_eval_uninit,
|
||||
InvalidVTablePtr { .. } => const_eval_invalid_vtable_ptr,
|
||||
InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => {
|
||||
const_eval_invalid_box_slice_meta
|
||||
}
|
||||
InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Ref } => {
|
||||
const_eval_invalid_ref_slice_meta
|
||||
}
|
||||
|
||||
InvalidMetaTooLarge { ptr_kind: PointerKind::Box } => const_eval_invalid_box_meta,
|
||||
InvalidMetaTooLarge { ptr_kind: PointerKind::Ref } => const_eval_invalid_ref_meta,
|
||||
UnalignedPtr { ptr_kind: PointerKind::Ref, .. } => const_eval_unaligned_ref,
|
||||
UnalignedPtr { ptr_kind: PointerKind::Box, .. } => const_eval_unaligned_box,
|
||||
|
||||
NullPtr { ptr_kind: PointerKind::Box } => const_eval_null_box,
|
||||
NullPtr { ptr_kind: PointerKind::Ref } => const_eval_null_ref,
|
||||
DanglingPtrNoProvenance { ptr_kind: PointerKind::Box, .. } => {
|
||||
const_eval_dangling_box_no_provenance
|
||||
}
|
||||
DanglingPtrNoProvenance { ptr_kind: PointerKind::Ref, .. } => {
|
||||
const_eval_dangling_ref_no_provenance
|
||||
}
|
||||
DanglingPtrOutOfBounds { ptr_kind: PointerKind::Box } => {
|
||||
const_eval_dangling_box_out_of_bounds
|
||||
}
|
||||
DanglingPtrOutOfBounds { ptr_kind: PointerKind::Ref } => {
|
||||
const_eval_dangling_ref_out_of_bounds
|
||||
}
|
||||
DanglingPtrUseAfterFree { ptr_kind: PointerKind::Box } => {
|
||||
const_eval_dangling_box_use_after_free
|
||||
}
|
||||
DanglingPtrUseAfterFree { ptr_kind: PointerKind::Ref } => {
|
||||
const_eval_dangling_ref_use_after_free
|
||||
}
|
||||
InvalidBool { .. } => const_eval_validation_invalid_bool,
|
||||
InvalidChar { .. } => const_eval_validation_invalid_char,
|
||||
InvalidFnPtr { .. } => const_eval_invalid_fn_ptr,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_args<G: EmissionGuarantee>(self, handler: &Handler, err: &mut DiagnosticBuilder<'_, G>) {
|
||||
use crate::fluent_generated as fluent;
|
||||
use rustc_middle::mir::interpret::ValidationErrorKind::*;
|
||||
|
||||
let message = if let Some(path) = self.path {
|
||||
handler.eagerly_translate_to_string(
|
||||
fluent::const_eval_invalid_value_with_path,
|
||||
[("path".into(), DiagnosticArgValue::Str(path.into()))].iter().map(|(a, b)| (a, b)),
|
||||
)
|
||||
} else {
|
||||
handler.eagerly_translate_to_string(fluent::const_eval_invalid_value, [].into_iter())
|
||||
};
|
||||
|
||||
err.set_arg("front_matter", message);
|
||||
|
||||
fn add_range_arg<G: EmissionGuarantee>(
|
||||
r: WrappingRange,
|
||||
max_hi: u128,
|
||||
handler: &Handler,
|
||||
err: &mut DiagnosticBuilder<'_, G>,
|
||||
) {
|
||||
let WrappingRange { start: lo, end: hi } = r;
|
||||
assert!(hi <= max_hi);
|
||||
let msg = if lo > hi {
|
||||
fluent::const_eval_range_wrapping
|
||||
} else if lo == hi {
|
||||
fluent::const_eval_range_singular
|
||||
} else if lo == 0 {
|
||||
assert!(hi < max_hi, "should not be printing if the range covers everything");
|
||||
fluent::const_eval_range_upper
|
||||
} else if hi == max_hi {
|
||||
assert!(lo > 0, "should not be printing if the range covers everything");
|
||||
fluent::const_eval_range_lower
|
||||
} else {
|
||||
fluent::const_eval_range
|
||||
};
|
||||
|
||||
let args = [
|
||||
("lo".into(), DiagnosticArgValue::Str(lo.to_string().into())),
|
||||
("hi".into(), DiagnosticArgValue::Str(hi.to_string().into())),
|
||||
];
|
||||
let args = args.iter().map(|(a, b)| (a, b));
|
||||
let message = handler.eagerly_translate_to_string(msg, args);
|
||||
err.set_arg("in_range", message);
|
||||
}
|
||||
|
||||
match self.kind {
|
||||
PtrToUninhabited { ty, .. } | UninhabitedVal { ty } => {
|
||||
err.set_arg("ty", ty);
|
||||
}
|
||||
ExpectedNonPtr { value }
|
||||
| InvalidEnumTag { value }
|
||||
| InvalidVTablePtr { value }
|
||||
| InvalidBool { value }
|
||||
| InvalidChar { value }
|
||||
| InvalidFnPtr { value } => {
|
||||
err.set_arg("value", value);
|
||||
}
|
||||
NullablePtrOutOfRange { range, max_value } | PtrOutOfRange { range, max_value } => {
|
||||
add_range_arg(range, max_value, handler, err)
|
||||
}
|
||||
OutOfRange { range, max_value, value } => {
|
||||
err.set_arg("value", value);
|
||||
add_range_arg(range, max_value, handler, err);
|
||||
}
|
||||
UnalignedPtr { required_bytes, found_bytes, .. } => {
|
||||
err.set_arg("required_bytes", required_bytes);
|
||||
err.set_arg("found_bytes", found_bytes);
|
||||
}
|
||||
DanglingPtrNoProvenance { pointer, .. } => {
|
||||
err.set_arg("pointer", pointer);
|
||||
}
|
||||
NullPtr { .. }
|
||||
| PtrToStatic { .. }
|
||||
| PtrToMut { .. }
|
||||
| MutableRefInConst
|
||||
| NullFnPtr
|
||||
| NeverVal
|
||||
| UnsafeCell
|
||||
| UninitEnumTag
|
||||
| UninitStr
|
||||
| Uninit { .. }
|
||||
| UninitVal
|
||||
| InvalidMetaSliceTooLarge { .. }
|
||||
| InvalidMetaTooLarge { .. }
|
||||
| DanglingPtrUseAfterFree { .. }
|
||||
| DanglingPtrOutOfBounds { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReportErrorExt for UnsupportedOpInfo {
|
||||
fn diagnostic_message(&self) -> DiagnosticMessage {
|
||||
use crate::fluent_generated::*;
|
||||
match self {
|
||||
UnsupportedOpInfo::Unsupported(s) => s.clone().into(),
|
||||
UnsupportedOpInfo::PartialPointerOverwrite(_) => const_eval_partial_pointer_overwrite,
|
||||
UnsupportedOpInfo::PartialPointerCopy(_) => const_eval_partial_pointer_copy,
|
||||
UnsupportedOpInfo::ReadPointerAsBytes => const_eval_read_pointer_as_bytes,
|
||||
UnsupportedOpInfo::ThreadLocalStatic(_) => const_eval_thread_local_static,
|
||||
UnsupportedOpInfo::ReadExternStatic(_) => const_eval_read_extern_static,
|
||||
}
|
||||
}
|
||||
fn add_args<G: EmissionGuarantee>(self, _: &Handler, builder: &mut DiagnosticBuilder<'_, G>) {
|
||||
use crate::fluent_generated::*;
|
||||
|
||||
use UnsupportedOpInfo::*;
|
||||
if let ReadPointerAsBytes | PartialPointerOverwrite(_) | PartialPointerCopy(_) = self {
|
||||
builder.help(const_eval_ptr_as_bytes_1);
|
||||
builder.help(const_eval_ptr_as_bytes_2);
|
||||
}
|
||||
match self {
|
||||
Unsupported(_) | ReadPointerAsBytes => {}
|
||||
PartialPointerOverwrite(ptr) | PartialPointerCopy(ptr) => {
|
||||
builder.set_arg("ptr", ptr);
|
||||
}
|
||||
ThreadLocalStatic(did) | ReadExternStatic(did) => {
|
||||
builder.set_arg("did", format!("{did:?}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ReportErrorExt for InterpError<'tcx> {
|
||||
fn diagnostic_message(&self) -> DiagnosticMessage {
|
||||
match self {
|
||||
InterpError::UndefinedBehavior(ub) => ub.diagnostic_message(),
|
||||
InterpError::Unsupported(e) => e.diagnostic_message(),
|
||||
InterpError::InvalidProgram(e) => e.diagnostic_message(),
|
||||
InterpError::ResourceExhaustion(e) => e.diagnostic_message(),
|
||||
InterpError::MachineStop(e) => e.diagnostic_message(),
|
||||
}
|
||||
}
|
||||
fn add_args<G: EmissionGuarantee>(
|
||||
self,
|
||||
handler: &Handler,
|
||||
builder: &mut DiagnosticBuilder<'_, G>,
|
||||
) {
|
||||
match self {
|
||||
InterpError::UndefinedBehavior(ub) => ub.add_args(handler, builder),
|
||||
InterpError::Unsupported(e) => e.add_args(handler, builder),
|
||||
InterpError::InvalidProgram(e) => e.add_args(handler, builder),
|
||||
InterpError::ResourceExhaustion(e) => e.add_args(handler, builder),
|
||||
InterpError::MachineStop(e) => e.add_args(&mut |name, value| {
|
||||
builder.set_arg(name, value);
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ReportErrorExt for InvalidProgramInfo<'tcx> {
|
||||
fn diagnostic_message(&self) -> DiagnosticMessage {
|
||||
use crate::fluent_generated::*;
|
||||
match self {
|
||||
InvalidProgramInfo::TooGeneric => const_eval_too_generic,
|
||||
InvalidProgramInfo::AlreadyReported(_) => const_eval_already_reported,
|
||||
InvalidProgramInfo::Layout(e) => e.diagnostic_message(),
|
||||
InvalidProgramInfo::FnAbiAdjustForForeignAbi(_) => {
|
||||
rustc_middle::error::middle_adjust_for_foreign_abi_error
|
||||
}
|
||||
InvalidProgramInfo::SizeOfUnsizedType(_) => const_eval_size_of_unsized,
|
||||
InvalidProgramInfo::UninitUnsizedLocal => const_eval_uninit_unsized_local,
|
||||
}
|
||||
}
|
||||
fn add_args<G: EmissionGuarantee>(
|
||||
self,
|
||||
handler: &Handler,
|
||||
builder: &mut DiagnosticBuilder<'_, G>,
|
||||
) {
|
||||
match self {
|
||||
InvalidProgramInfo::TooGeneric
|
||||
| InvalidProgramInfo::AlreadyReported(_)
|
||||
| InvalidProgramInfo::UninitUnsizedLocal => {}
|
||||
InvalidProgramInfo::Layout(e) => {
|
||||
let diag: DiagnosticBuilder<'_, ()> = e.into_diagnostic().into_diagnostic(handler);
|
||||
for (name, val) in diag.args() {
|
||||
builder.set_arg(name.clone(), val.clone());
|
||||
}
|
||||
diag.cancel();
|
||||
}
|
||||
InvalidProgramInfo::FnAbiAdjustForForeignAbi(
|
||||
AdjustForForeignAbiError::Unsupported { arch, abi },
|
||||
) => {
|
||||
builder.set_arg("arch", arch);
|
||||
builder.set_arg("abi", abi.name());
|
||||
}
|
||||
InvalidProgramInfo::SizeOfUnsizedType(ty) => {
|
||||
builder.set_arg("ty", ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReportErrorExt for ResourceExhaustionInfo {
|
||||
fn diagnostic_message(&self) -> DiagnosticMessage {
|
||||
use crate::fluent_generated::*;
|
||||
match self {
|
||||
ResourceExhaustionInfo::StackFrameLimitReached => const_eval_stack_frame_limit_reached,
|
||||
ResourceExhaustionInfo::MemoryExhausted => const_eval_memory_exhausted,
|
||||
ResourceExhaustionInfo::AddressSpaceFull => const_eval_address_space_full,
|
||||
}
|
||||
}
|
||||
fn add_args<G: EmissionGuarantee>(self, _: &Handler, _: &mut DiagnosticBuilder<'_, G>) {}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ use super::{
|
|||
util::ensure_monomorphic_enough, FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy,
|
||||
};
|
||||
|
||||
use crate::fluent_generated as fluent;
|
||||
|
||||
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
pub fn cast(
|
||||
&mut self,
|
||||
|
@ -138,12 +140,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
assert!(src.layout.is_sized());
|
||||
assert!(dest.layout.is_sized());
|
||||
if src.layout.size != dest.layout.size {
|
||||
throw_ub_format!(
|
||||
"transmuting from {}-byte type to {}-byte type: `{}` -> `{}`",
|
||||
src.layout.size.bytes(),
|
||||
dest.layout.size.bytes(),
|
||||
src.layout.ty,
|
||||
dest.layout.ty,
|
||||
let src_bytes = src.layout.size.bytes();
|
||||
let dest_bytes = dest.layout.size.bytes();
|
||||
let src_ty = format!("{}", src.layout.ty);
|
||||
let dest_ty = format!("{}", dest.layout.ty);
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_invalid_transmute,
|
||||
src_bytes = src_bytes,
|
||||
dest_bytes = dest_bytes,
|
||||
src = src_ty,
|
||||
dest = dest_ty,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -363,7 +369,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let old_vptr = old_vptr.to_pointer(self)?;
|
||||
let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?;
|
||||
if old_trait != data_a.principal() {
|
||||
throw_ub_format!("upcast on a pointer whose vtable does not match its type");
|
||||
throw_ub_custom!(fluent::const_eval_upcast_mismatch);
|
||||
}
|
||||
let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?;
|
||||
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::cell::Cell;
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use either::{Either, Left, Right};
|
||||
|
||||
|
@ -8,7 +7,7 @@ use hir::CRATE_HIR_ID;
|
|||
use rustc_hir::{self as hir, def_id::DefId, definitions::DefPathData};
|
||||
use rustc_index::IndexVec;
|
||||
use rustc_middle::mir;
|
||||
use rustc_middle::mir::interpret::{ErrorHandled, InterpError, ReportedErrorInfo};
|
||||
use rustc_middle::mir::interpret::{ErrorHandled, InterpError, InvalidMetaKind, ReportedErrorInfo};
|
||||
use rustc_middle::query::TyCtxtAt;
|
||||
use rustc_middle::ty::layout::{
|
||||
self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOf, LayoutOfHelpers,
|
||||
|
@ -25,6 +24,8 @@ use super::{
|
|||
MemPlaceMeta, Memory, MemoryKind, Operand, Place, PlaceTy, PointerArithmetic, Provenance,
|
||||
Scalar, StackPopJump,
|
||||
};
|
||||
use crate::errors::{self, ErroneousConstUsed};
|
||||
use crate::fluent_generated as fluent;
|
||||
use crate::util;
|
||||
|
||||
pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
|
||||
|
@ -247,6 +248,7 @@ impl<'mir, 'tcx, Prov: Provenance, Extra> Frame<'mir, 'tcx, Prov, Extra> {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: only used by miri, should be removed once translatable.
|
||||
impl<'tcx> fmt::Display for FrameInfo<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
ty::tls::with(|tcx| {
|
||||
|
@ -264,6 +266,21 @@ impl<'tcx> fmt::Display for FrameInfo<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx> FrameInfo<'tcx> {
|
||||
pub fn as_note(&self, tcx: TyCtxt<'tcx>) -> errors::FrameNote {
|
||||
let span = self.span;
|
||||
if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr {
|
||||
errors::FrameNote { where_: "closure", span, instance: String::new(), times: 0 }
|
||||
} else {
|
||||
let instance = format!("{}", self.instance);
|
||||
// Note: this triggers a `good_path_bug` state, which means that if we ever get here
|
||||
// we must emit a diagnostic. We should never display a `FrameInfo` unless we
|
||||
// actually want to emit a warning or error to the user.
|
||||
errors::FrameNote { where_: "instance", span, instance, times: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for InterpCx<'mir, 'tcx, M> {
|
||||
#[inline]
|
||||
fn data_layout(&self) -> &TargetDataLayout {
|
||||
|
@ -620,7 +637,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
|
||||
// Check if this brought us over the size limit.
|
||||
if size > self.max_size_of_val() {
|
||||
throw_ub!(InvalidMeta("total size is bigger than largest supported object"));
|
||||
throw_ub!(InvalidMeta(InvalidMetaKind::TooBig));
|
||||
}
|
||||
Ok(Some((size, align)))
|
||||
}
|
||||
|
@ -638,7 +655,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let size = elem.size.bytes().saturating_mul(len); // we rely on `max_size_of_val` being smaller than `u64::MAX`.
|
||||
let size = Size::from_bytes(size);
|
||||
if size > self.max_size_of_val() {
|
||||
throw_ub!(InvalidMeta("slice is bigger than largest supported object"));
|
||||
throw_ub!(InvalidMeta(InvalidMetaKind::SliceTooBig));
|
||||
}
|
||||
Ok(Some((size, elem.align.abi)))
|
||||
}
|
||||
|
@ -746,7 +763,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
mir::UnwindAction::Cleanup(block) => Left(mir::Location { block, statement_index: 0 }),
|
||||
mir::UnwindAction::Continue => Right(self.frame_mut().body.span),
|
||||
mir::UnwindAction::Unreachable => {
|
||||
throw_ub_format!("unwinding past a stack frame that does not allow unwinding")
|
||||
throw_ub_custom!(fluent::const_eval_unreachable_unwind);
|
||||
}
|
||||
mir::UnwindAction::Terminate => {
|
||||
self.frame_mut().loc = Right(self.frame_mut().body.span);
|
||||
|
@ -785,7 +802,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
}
|
||||
);
|
||||
if unwinding && self.frame_idx() == 0 {
|
||||
throw_ub_format!("unwinding past the topmost frame of the stack");
|
||||
throw_ub_custom!(fluent::const_eval_unwind_past_top);
|
||||
}
|
||||
|
||||
// Copy return value. Must of course happen *before* we deallocate the locals.
|
||||
|
@ -873,7 +890,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
// StorageLive expects the local to be dead, and marks it live.
|
||||
let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
|
||||
if !matches!(old, LocalValue::Dead) {
|
||||
throw_ub_format!("StorageLive on a local that was already live");
|
||||
throw_ub_custom!(fluent::const_eval_double_storage_live);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -916,7 +933,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
ErrorHandled::Reported(err) => {
|
||||
if !err.is_tainted_by_errors() && let Some(span) = span {
|
||||
// To make it easier to figure out where this error comes from, also add a note at the current location.
|
||||
self.tcx.sess.span_note_without_error(span, "erroneous constant used");
|
||||
self.tcx.sess.emit_note(ErroneousConstUsed { span });
|
||||
}
|
||||
err_inval!(AlreadyReported(err))
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ use super::{
|
|||
ValueVisitor,
|
||||
};
|
||||
use crate::const_eval;
|
||||
use crate::errors::{DanglingPtrInFinal, UnsupportedUntypedPointer};
|
||||
|
||||
pub trait CompileTimeMachine<'mir, 'tcx, T> = Machine<
|
||||
'mir,
|
||||
|
@ -320,10 +321,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
|
|||
}
|
||||
}
|
||||
|
||||
/// How a constant value should be interned.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
|
||||
pub enum InternKind {
|
||||
/// The `mutability` of the static, ignoring the type which may have interior mutability.
|
||||
Static(hir::Mutability),
|
||||
/// A `const` item
|
||||
Constant,
|
||||
Promoted,
|
||||
}
|
||||
|
@ -388,8 +391,7 @@ pub fn intern_const_alloc_recursive<
|
|||
ecx.tcx.sess.delay_span_bug(
|
||||
ecx.tcx.span,
|
||||
format!(
|
||||
"error during interning should later cause validation failure: {}",
|
||||
error
|
||||
"error during interning should later cause validation failure: {error:?}"
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -425,14 +427,16 @@ pub fn intern_const_alloc_recursive<
|
|||
// immutability is so important.
|
||||
alloc.mutability = Mutability::Not;
|
||||
}
|
||||
// If it's a constant, we should not have any "leftovers" as everything
|
||||
// is tracked by const-checking.
|
||||
// FIXME: downgrade this to a warning? It rejects some legitimate consts,
|
||||
// such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
|
||||
//
|
||||
// NOTE: it looks likes this code path is only reachable when we try to intern
|
||||
// something that cannot be promoted, which in constants means values that have
|
||||
// drop glue, such as the example above.
|
||||
InternKind::Constant => {
|
||||
// If it's a constant, we should not have any "leftovers" as everything
|
||||
// is tracked by const-checking.
|
||||
// FIXME: downgrade this to a warning? It rejects some legitimate consts,
|
||||
// such as `const CONST_RAW: *const Vec<i32> = &Vec::new() as *const _;`.
|
||||
ecx.tcx
|
||||
.sess
|
||||
.span_err(ecx.tcx.span, "untyped pointers are not allowed in constant");
|
||||
ecx.tcx.sess.emit_err(UnsupportedUntypedPointer { span: ecx.tcx.span });
|
||||
// For better errors later, mark the allocation as immutable.
|
||||
alloc.mutability = Mutability::Not;
|
||||
}
|
||||
|
@ -447,10 +451,7 @@ pub fn intern_const_alloc_recursive<
|
|||
} else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) {
|
||||
// Codegen does not like dangling pointers, and generally `tcx` assumes that
|
||||
// all allocations referenced anywhere actually exist. So, make sure we error here.
|
||||
let reported = ecx
|
||||
.tcx
|
||||
.sess
|
||||
.span_err(ecx.tcx.span, "encountered dangling pointer in final constant");
|
||||
let reported = ecx.tcx.sess.emit_err(DanglingPtrInFinal { span: ecx.tcx.span });
|
||||
return Err(reported);
|
||||
} else if ecx.tcx.try_get_global_alloc(alloc_id).is_none() {
|
||||
// We have hit an `AllocId` that is neither in local or global memory and isn't
|
||||
|
|
|
@ -22,6 +22,8 @@ use super::{
|
|||
Pointer,
|
||||
};
|
||||
|
||||
use crate::fluent_generated as fluent;
|
||||
|
||||
mod caller_location;
|
||||
|
||||
fn numeric_intrinsic<Prov>(name: Symbol, bits: u128, kind: Primitive) -> Scalar<Prov> {
|
||||
|
@ -198,15 +200,18 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
ty
|
||||
),
|
||||
};
|
||||
let (nonzero, intrinsic_name) = match intrinsic_name {
|
||||
let (nonzero, actual_intrinsic_name) = match intrinsic_name {
|
||||
sym::cttz_nonzero => (true, sym::cttz),
|
||||
sym::ctlz_nonzero => (true, sym::ctlz),
|
||||
other => (false, other),
|
||||
};
|
||||
if nonzero && bits == 0 {
|
||||
throw_ub_format!("`{}_nonzero` called on 0", intrinsic_name);
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_call_nonzero_intrinsic,
|
||||
name = intrinsic_name,
|
||||
);
|
||||
}
|
||||
let out_val = numeric_intrinsic(intrinsic_name, bits, kind);
|
||||
let out_val = numeric_intrinsic(actual_intrinsic_name, bits, kind);
|
||||
self.write_scalar(out_val, dest)?;
|
||||
}
|
||||
sym::saturating_add | sym::saturating_sub => {
|
||||
|
@ -253,9 +258,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let layout = self.layout_of(substs.type_at(0))?;
|
||||
let r_val = r.to_scalar().to_bits(layout.size)?;
|
||||
if let sym::unchecked_shl | sym::unchecked_shr = intrinsic_name {
|
||||
throw_ub_format!("overflowing shift by {} in `{}`", r_val, intrinsic_name);
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_overflow_shift,
|
||||
val = r_val,
|
||||
name = intrinsic_name
|
||||
);
|
||||
} else {
|
||||
throw_ub_format!("overflow executing `{}`", intrinsic_name);
|
||||
throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name);
|
||||
}
|
||||
}
|
||||
self.write_scalar(val, dest)?;
|
||||
|
@ -314,17 +323,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
(Err(_), _) | (_, Err(_)) => {
|
||||
// We managed to find a valid allocation for one pointer, but not the other.
|
||||
// That means they are definitely not pointing to the same allocation.
|
||||
throw_ub_format!(
|
||||
"`{}` called on pointers into different allocations",
|
||||
intrinsic_name
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_different_allocations,
|
||||
name = intrinsic_name,
|
||||
);
|
||||
}
|
||||
(Ok((a_alloc_id, a_offset, _)), Ok((b_alloc_id, b_offset, _))) => {
|
||||
// Found allocation for both. They must be into the same allocation.
|
||||
if a_alloc_id != b_alloc_id {
|
||||
throw_ub_format!(
|
||||
"`{}` called on pointers into different allocations",
|
||||
intrinsic_name
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_different_allocations,
|
||||
name = intrinsic_name,
|
||||
);
|
||||
}
|
||||
// Use these offsets for distance calculation.
|
||||
|
@ -344,11 +353,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
if overflowed {
|
||||
// a < b
|
||||
if intrinsic_name == sym::ptr_offset_from_unsigned {
|
||||
throw_ub_format!(
|
||||
"`{}` called when first pointer has smaller offset than second: {} < {}",
|
||||
intrinsic_name,
|
||||
a_offset,
|
||||
b_offset,
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_unsigned_offset_from_overflow,
|
||||
a_offset = a_offset,
|
||||
b_offset = b_offset,
|
||||
);
|
||||
}
|
||||
// The signed form of the intrinsic allows this. If we interpret the
|
||||
|
@ -356,9 +364,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
// seems *positive*, they were more than isize::MAX apart.
|
||||
let dist = val.to_target_isize(self)?;
|
||||
if dist >= 0 {
|
||||
throw_ub_format!(
|
||||
"`{}` called when first pointer is too far before second",
|
||||
intrinsic_name
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_offset_from_underflow,
|
||||
name = intrinsic_name,
|
||||
);
|
||||
}
|
||||
dist
|
||||
|
@ -368,9 +376,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
// If converting to isize produced a *negative* result, we had an overflow
|
||||
// because they were more than isize::MAX apart.
|
||||
if dist < 0 {
|
||||
throw_ub_format!(
|
||||
"`{}` called when first pointer is too far ahead of second",
|
||||
intrinsic_name
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_offset_from_overflow,
|
||||
name = intrinsic_name,
|
||||
);
|
||||
}
|
||||
dist
|
||||
|
@ -513,7 +521,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let op = self.eval_operand(op, None)?;
|
||||
let cond = self.read_scalar(&op)?.to_bool()?;
|
||||
if !cond {
|
||||
throw_ub_format!("`assume` called with `false`");
|
||||
throw_ub_custom!(fluent::const_eval_assume_false);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -542,7 +550,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let (res, overflow, _ty) = self.overflowing_binary_op(BinOp::Rem, &a, &b)?;
|
||||
assert!(!overflow); // All overflow is UB, so this should never return on overflow.
|
||||
if res.assert_bits(a.layout.size) != 0 {
|
||||
throw_ub_format!("exact_div: {} cannot be divided by {} without remainder", a, b)
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_exact_div_has_remainder,
|
||||
a = format!("{a}"),
|
||||
b = format!("{b}")
|
||||
)
|
||||
}
|
||||
// `Rem` says this is all right, so we can let `Div` do its job.
|
||||
self.binop_ignore_overflow(BinOp::Div, &a, &b, dest)
|
||||
|
@ -638,9 +650,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
// `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
|
||||
// but no actual allocation can be big enough for the difference to be noticeable.
|
||||
let size = size.checked_mul(count, self).ok_or_else(|| {
|
||||
err_ub_format!(
|
||||
"overflow computing total size of `{}`",
|
||||
if nonoverlapping { "copy_nonoverlapping" } else { "copy" }
|
||||
err_ub_custom!(
|
||||
fluent::const_eval_size_overflow,
|
||||
name = if nonoverlapping { "copy_nonoverlapping" } else { "copy" }
|
||||
)
|
||||
})?;
|
||||
|
||||
|
@ -664,10 +676,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
|
||||
// `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
|
||||
// but no actual allocation can be big enough for the difference to be noticeable.
|
||||
let len = layout
|
||||
.size
|
||||
.checked_mul(count, self)
|
||||
.ok_or_else(|| err_ub_format!("overflow computing total size of `write_bytes`"))?;
|
||||
let len = layout.size.checked_mul(count, self).ok_or_else(|| {
|
||||
err_ub_custom!(fluent::const_eval_size_overflow, name = "write_bytes")
|
||||
})?;
|
||||
|
||||
let bytes = std::iter::repeat(byte).take(len.bytes_usize());
|
||||
self.write_bytes_ptr(dst, bytes)
|
||||
|
@ -691,7 +702,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
return Ok(&[]);
|
||||
};
|
||||
if alloc_ref.has_provenance() {
|
||||
throw_ub_format!("`raw_eq` on bytes with provenance");
|
||||
throw_ub_custom!(fluent::const_eval_raw_eq_with_provenance);
|
||||
}
|
||||
alloc_ref.get_bytes_strip_provenance()
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TyCtxt};
|
|||
use rustc_target::abi::{Align, HasDataLayout, Size};
|
||||
|
||||
use crate::const_eval::CheckAlignment;
|
||||
use crate::fluent_generated as fluent;
|
||||
|
||||
use super::{
|
||||
alloc_range, AllocBytes, AllocId, AllocMap, AllocRange, Allocation, CheckInAllocMsg,
|
||||
|
@ -200,7 +201,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
align: Align,
|
||||
kind: MemoryKind<M::MemoryKind>,
|
||||
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
|
||||
let alloc = Allocation::uninit(size, align, M::PANIC_ON_ALLOC_FAIL)?;
|
||||
let alloc = if M::PANIC_ON_ALLOC_FAIL {
|
||||
Allocation::uninit(size, align)
|
||||
} else {
|
||||
Allocation::try_uninit(size, align)?
|
||||
};
|
||||
self.allocate_raw_ptr(alloc, kind)
|
||||
}
|
||||
|
||||
|
@ -242,9 +247,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
|
||||
let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr)?;
|
||||
if offset.bytes() != 0 {
|
||||
throw_ub_format!(
|
||||
"reallocating {:?} which does not point to the beginning of an object",
|
||||
ptr
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_realloc_or_alloc_with_offset,
|
||||
ptr = format!("{ptr:?}"),
|
||||
kind = "realloc"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -280,9 +286,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
trace!("deallocating: {alloc_id:?}");
|
||||
|
||||
if offset.bytes() != 0 {
|
||||
throw_ub_format!(
|
||||
"deallocating {:?} which does not point to the beginning of an object",
|
||||
ptr
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_realloc_or_alloc_with_offset,
|
||||
ptr = format!("{ptr:?}"),
|
||||
kind = "dealloc",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -290,13 +297,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
// Deallocating global memory -- always an error
|
||||
return Err(match self.tcx.try_get_global_alloc(alloc_id) {
|
||||
Some(GlobalAlloc::Function(..)) => {
|
||||
err_ub_format!("deallocating {alloc_id:?}, which is a function")
|
||||
err_ub_custom!(
|
||||
fluent::const_eval_invalid_dealloc,
|
||||
alloc_id = alloc_id,
|
||||
kind = "fn",
|
||||
)
|
||||
}
|
||||
Some(GlobalAlloc::VTable(..)) => {
|
||||
err_ub_format!("deallocating {alloc_id:?}, which is a vtable")
|
||||
err_ub_custom!(
|
||||
fluent::const_eval_invalid_dealloc,
|
||||
alloc_id = alloc_id,
|
||||
kind = "vtable",
|
||||
)
|
||||
}
|
||||
Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => {
|
||||
err_ub_format!("deallocating {alloc_id:?}, which is static memory")
|
||||
err_ub_custom!(
|
||||
fluent::const_eval_invalid_dealloc,
|
||||
alloc_id = alloc_id,
|
||||
kind = "static_mem"
|
||||
)
|
||||
}
|
||||
None => err_ub!(PointerUseAfterFree(alloc_id)),
|
||||
}
|
||||
|
@ -304,21 +323,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
};
|
||||
|
||||
if alloc.mutability.is_not() {
|
||||
throw_ub_format!("deallocating immutable allocation {alloc_id:?}");
|
||||
throw_ub_custom!(fluent::const_eval_dealloc_immutable, alloc = alloc_id,);
|
||||
}
|
||||
if alloc_kind != kind {
|
||||
throw_ub_format!(
|
||||
"deallocating {alloc_id:?}, which is {alloc_kind} memory, using {kind} deallocation operation"
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_dealloc_kind_mismatch,
|
||||
alloc = alloc_id,
|
||||
alloc_kind = format!("{alloc_kind}"),
|
||||
kind = format!("{kind}"),
|
||||
);
|
||||
}
|
||||
if let Some((size, align)) = old_size_and_align {
|
||||
if size != alloc.size() || align != alloc.align {
|
||||
throw_ub_format!(
|
||||
"incorrect layout on deallocation: {alloc_id:?} has size {} and alignment {}, but gave size {} and alignment {}",
|
||||
alloc.size().bytes(),
|
||||
alloc.align.bytes(),
|
||||
size.bytes(),
|
||||
align.bytes(),
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_dealloc_incorrect_layout,
|
||||
alloc = alloc_id,
|
||||
size = alloc.size().bytes(),
|
||||
align = alloc.align.bytes(),
|
||||
size_found = size.bytes(),
|
||||
align_found = align.bytes(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1166,7 +1189,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
if (src_offset <= dest_offset && src_offset + size > dest_offset)
|
||||
|| (dest_offset <= src_offset && dest_offset + size > src_offset)
|
||||
{
|
||||
throw_ub_format!("copy_nonoverlapping called on overlapping ranges")
|
||||
throw_ub_custom!(fluent::const_eval_copy_nonoverlapping_overlapping);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ use super::{
|
|||
FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, Operand,
|
||||
PlaceTy, Scalar, StackPopCleanup,
|
||||
};
|
||||
use crate::fluent_generated as fluent;
|
||||
|
||||
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
pub(super) fn eval_terminator(
|
||||
|
@ -172,7 +173,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
InlineAsm { template, ref operands, options, destination, .. } => {
|
||||
M::eval_inline_asm(self, template, operands, options)?;
|
||||
if options.contains(InlineAsmOptions::NORETURN) {
|
||||
throw_ub_format!("returned from noreturn inline assembly");
|
||||
throw_ub_custom!(fluent::const_eval_noreturn_asm_returned);
|
||||
}
|
||||
self.go_to_block(
|
||||
destination
|
||||
|
@ -288,15 +289,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
return Ok(());
|
||||
}
|
||||
// Find next caller arg.
|
||||
let (caller_arg, caller_abi) = caller_args.next().ok_or_else(|| {
|
||||
err_ub_format!("calling a function with fewer arguments than it requires")
|
||||
})?;
|
||||
let Some((caller_arg, caller_abi)) = caller_args.next() else {
|
||||
throw_ub_custom!(fluent::const_eval_not_enough_caller_args);
|
||||
};
|
||||
// Now, check
|
||||
if !Self::check_argument_compat(caller_abi, callee_abi) {
|
||||
throw_ub_format!(
|
||||
"calling a function with argument of type {:?} passing data of type {:?}",
|
||||
callee_arg.layout.ty,
|
||||
caller_arg.layout.ty
|
||||
let callee_ty = format!("{}", callee_arg.layout.ty);
|
||||
let caller_ty = format!("{}", caller_arg.layout.ty);
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_incompatible_types,
|
||||
callee_ty = callee_ty,
|
||||
caller_ty = caller_ty,
|
||||
)
|
||||
}
|
||||
// Special handling for unsized parameters.
|
||||
|
@ -398,10 +401,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
|
||||
if M::enforce_abi(self) {
|
||||
if caller_fn_abi.conv != callee_fn_abi.conv {
|
||||
throw_ub_format!(
|
||||
"calling a function with calling convention {:?} using calling convention {:?}",
|
||||
callee_fn_abi.conv,
|
||||
caller_fn_abi.conv
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_incompatible_calling_conventions,
|
||||
callee_conv = format!("{:?}", callee_fn_abi.conv),
|
||||
caller_conv = format!("{:?}", caller_fn_abi.conv),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -508,15 +511,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
"mismatch between callee ABI and callee body arguments"
|
||||
);
|
||||
if caller_args.next().is_some() {
|
||||
throw_ub_format!("calling a function with more arguments than it expected")
|
||||
throw_ub_custom!(fluent::const_eval_too_many_caller_args);
|
||||
}
|
||||
// Don't forget to check the return type!
|
||||
if !Self::check_argument_compat(&caller_fn_abi.ret, &callee_fn_abi.ret) {
|
||||
throw_ub_format!(
|
||||
"calling a function with return type {:?} passing \
|
||||
return place of type {:?}",
|
||||
callee_fn_abi.ret.layout.ty,
|
||||
caller_fn_abi.ret.layout.ty,
|
||||
let callee_ty = format!("{}", callee_fn_abi.ret.layout.ty);
|
||||
let caller_ty = format!("{}", caller_fn_abi.ret.layout.ty);
|
||||
throw_ub_custom!(
|
||||
fluent::const_eval_incompatible_return_types,
|
||||
callee_ty = callee_ty,
|
||||
caller_ty = caller_ty,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
@ -587,9 +591,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let (recv, vptr) = self.unpack_dyn_star(&receiver_place.into())?;
|
||||
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
|
||||
if dyn_trait != data.principal() {
|
||||
throw_ub_format!(
|
||||
"`dyn*` call on a pointer whose vtable does not match its type"
|
||||
);
|
||||
throw_ub_custom!(fluent::const_eval_dyn_star_call_vtable_mismatch);
|
||||
}
|
||||
let recv = recv.assert_mem_place(); // we passed an MPlaceTy to `unpack_dyn_star` so we definitely still have one
|
||||
|
||||
|
@ -609,9 +611,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?;
|
||||
let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?;
|
||||
if dyn_trait != data.principal() {
|
||||
throw_ub_format!(
|
||||
"`dyn` call on a pointer whose vtable does not match its type"
|
||||
);
|
||||
throw_ub_custom!(fluent::const_eval_dyn_call_vtable_mismatch);
|
||||
}
|
||||
|
||||
// It might be surprising that we use a pointer as the receiver even if this
|
||||
|
@ -623,7 +623,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
// Now determine the actual method to call. We can do that in two different ways and
|
||||
// compare them to ensure everything fits.
|
||||
let Some(ty::VtblEntry::Method(fn_inst)) = self.get_vtable_entries(vptr)?.get(idx).copied() else {
|
||||
throw_ub_format!("`dyn` call trying to call something that is not a method")
|
||||
// FIXME(fee1-dead) these could be variants of the UB info enum instead of this
|
||||
throw_ub_custom!(fluent::const_eval_dyn_call_not_a_method);
|
||||
};
|
||||
trace!("Virtual call dispatches to {fn_inst:#?}");
|
||||
if cfg!(debug_assertions) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
//! That's useful because it means other passes (e.g. promotion) can rely on `const`s
|
||||
//! to be const-safe.
|
||||
|
||||
use std::fmt::{Display, Write};
|
||||
use std::fmt::Write;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use either::{Left, Right};
|
||||
|
@ -12,7 +12,10 @@ use either::{Left, Right};
|
|||
use rustc_ast::Mutability;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir as hir;
|
||||
use rustc_middle::mir::interpret::InterpError;
|
||||
use rustc_middle::mir::interpret::{
|
||||
ExpectedKind, InterpError, InvalidMetaKind, PointerKind, ValidationErrorInfo,
|
||||
ValidationErrorKind, ValidationErrorKind::*,
|
||||
};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
|
@ -30,14 +33,7 @@ use super::{
|
|||
};
|
||||
|
||||
macro_rules! throw_validation_failure {
|
||||
($where:expr, { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )?) => {{
|
||||
let mut msg = String::new();
|
||||
msg.push_str("encountered ");
|
||||
write!(&mut msg, $($what_fmt)*).unwrap();
|
||||
$(
|
||||
msg.push_str(", but expected ");
|
||||
write!(&mut msg, $($expected_fmt)*).unwrap();
|
||||
)?
|
||||
($where:expr, $kind: expr) => {{
|
||||
let where_ = &$where;
|
||||
let path = if !where_.is_empty() {
|
||||
let mut path = String::new();
|
||||
|
@ -46,7 +42,8 @@ macro_rules! throw_validation_failure {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
throw_ub!(ValidationFailure { path, msg })
|
||||
|
||||
throw_ub!(Validation(ValidationErrorInfo { path, kind: $kind }))
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -82,22 +79,22 @@ macro_rules! throw_validation_failure {
|
|||
///
|
||||
macro_rules! try_validation {
|
||||
($e:expr, $where:expr,
|
||||
$( $( $p:pat_param )|+ => { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )? ),+ $(,)?
|
||||
$( $( $p:pat_param )|+ => $kind: expr ),+ $(,)?
|
||||
) => {{
|
||||
match $e {
|
||||
Ok(x) => x,
|
||||
// We catch the error and turn it into a validation failure. We are okay with
|
||||
// allocation here as this can only slow down builds that fail anyway.
|
||||
Err(e) => match e.kind() {
|
||||
Err(e) => match e.into_parts() {
|
||||
$(
|
||||
InterpError::UndefinedBehavior($($p)|+) =>
|
||||
(InterpError::UndefinedBehavior($($p)|+), _) =>
|
||||
throw_validation_failure!(
|
||||
$where,
|
||||
{ $( $what_fmt )* } $( expected { $( $expected_fmt )* } )?
|
||||
$kind
|
||||
)
|
||||
),+,
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => Err::<!, _>(e)?,
|
||||
(e, rest) => Err::<!, _>($crate::interpret::InterpErrorInfo::from_parts(e, rest))?,
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
@ -160,6 +157,7 @@ impl<T: Copy + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH>
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME make this translatable as well?
|
||||
/// Format a path
|
||||
fn write_path(out: &mut String, path: &[PathElem]) {
|
||||
use self::PathElem::*;
|
||||
|
@ -185,26 +183,6 @@ fn write_path(out: &mut String, path: &[PathElem]) {
|
|||
}
|
||||
}
|
||||
|
||||
// Formats such that a sentence like "expected something {}" to mean
|
||||
// "expected something <in the given range>" makes sense.
|
||||
fn wrapping_range_format(r: WrappingRange, max_hi: u128) -> String {
|
||||
let WrappingRange { start: lo, end: hi } = r;
|
||||
assert!(hi <= max_hi);
|
||||
if lo > hi {
|
||||
format!("less or equal to {}, or greater or equal to {}", hi, lo)
|
||||
} else if lo == hi {
|
||||
format!("equal to {}", lo)
|
||||
} else if lo == 0 {
|
||||
assert!(hi < max_hi, "should not be printing if the range covers everything");
|
||||
format!("less or equal to {}", hi)
|
||||
} else if hi == max_hi {
|
||||
assert!(lo > 0, "should not be printing if the range covers everything");
|
||||
format!("greater or equal to {}", lo)
|
||||
} else {
|
||||
format!("in the range {:?}", r)
|
||||
}
|
||||
}
|
||||
|
||||
struct ValidityVisitor<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> {
|
||||
/// The `path` may be pushed to, but the part that is present when a function
|
||||
/// starts must not be changed! `visit_fields` and `visit_array` rely on
|
||||
|
@ -311,19 +289,19 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
fn read_immediate(
|
||||
&self,
|
||||
op: &OpTy<'tcx, M::Provenance>,
|
||||
expected: impl Display,
|
||||
expected: ExpectedKind,
|
||||
) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> {
|
||||
Ok(try_validation!(
|
||||
self.ecx.read_immediate(op),
|
||||
self.path,
|
||||
InvalidUninitBytes(None) => { "uninitialized memory" } expected { "{expected}" }
|
||||
InvalidUninitBytes(None) => Uninit { expected }
|
||||
))
|
||||
}
|
||||
|
||||
fn read_scalar(
|
||||
&self,
|
||||
op: &OpTy<'tcx, M::Provenance>,
|
||||
expected: impl Display,
|
||||
expected: ExpectedKind,
|
||||
) -> InterpResult<'tcx, Scalar<M::Provenance>> {
|
||||
Ok(self.read_immediate(op, expected)?.to_scalar())
|
||||
}
|
||||
|
@ -342,8 +320,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
self.ecx.get_ptr_vtable(vtable),
|
||||
self.path,
|
||||
DanglingIntPointer(..) |
|
||||
InvalidVTablePointer(..) =>
|
||||
{ "{vtable}" } expected { "a vtable pointer" },
|
||||
InvalidVTablePointer(..) => InvalidVTablePtr { value: format!("{vtable}") }
|
||||
);
|
||||
// FIXME: check if the type/trait match what ty::Dynamic says?
|
||||
}
|
||||
|
@ -366,10 +343,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
fn check_safe_pointer(
|
||||
&mut self,
|
||||
value: &OpTy<'tcx, M::Provenance>,
|
||||
kind: &str,
|
||||
ptr_kind: PointerKind,
|
||||
) -> InterpResult<'tcx> {
|
||||
let place =
|
||||
self.ecx.ref_to_mplace(&self.read_immediate(value, format_args!("a {kind}"))?)?;
|
||||
let place = self.ecx.ref_to_mplace(&self.read_immediate(value, ptr_kind.into())?)?;
|
||||
// Handle wide pointers.
|
||||
// Check metadata early, for better diagnostics
|
||||
if place.layout.is_unsized() {
|
||||
|
@ -379,7 +355,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
let size_and_align = try_validation!(
|
||||
self.ecx.size_and_align_of_mplace(&place),
|
||||
self.path,
|
||||
InvalidMeta(msg) => { "invalid {} metadata: {}", kind, msg },
|
||||
InvalidMeta(msg) => match msg {
|
||||
InvalidMetaKind::SliceTooBig => InvalidMetaSliceTooLarge { ptr_kind },
|
||||
InvalidMetaKind::TooBig => InvalidMetaTooLarge { ptr_kind },
|
||||
}
|
||||
);
|
||||
let (size, align) = size_and_align
|
||||
// for the purpose of validity, consider foreign types to have
|
||||
|
@ -395,31 +374,30 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message
|
||||
),
|
||||
self.path,
|
||||
AlignmentCheckFailed { required, has } =>
|
||||
{
|
||||
"an unaligned {kind} (required {} byte alignment but found {})",
|
||||
required.bytes(),
|
||||
has.bytes(),
|
||||
},
|
||||
DanglingIntPointer(0, _) =>
|
||||
{ "a null {kind}" },
|
||||
DanglingIntPointer(i, _) =>
|
||||
{
|
||||
"a dangling {kind} ({pointer} has no provenance)",
|
||||
pointer = Pointer::<Option<AllocId>>::from_addr_invalid(*i),
|
||||
},
|
||||
PointerOutOfBounds { .. } =>
|
||||
{ "a dangling {kind} (going beyond the bounds of its allocation)" },
|
||||
AlignmentCheckFailed { required, has } => UnalignedPtr {
|
||||
ptr_kind,
|
||||
required_bytes: required.bytes(),
|
||||
found_bytes: has.bytes()
|
||||
},
|
||||
DanglingIntPointer(0, _) => NullPtr { ptr_kind },
|
||||
DanglingIntPointer(i, _) => DanglingPtrNoProvenance {
|
||||
ptr_kind,
|
||||
// FIXME this says "null pointer" when null but we need translate
|
||||
pointer: format!("{}", Pointer::<Option<AllocId>>::from_addr_invalid(i))
|
||||
},
|
||||
PointerOutOfBounds { .. } => DanglingPtrOutOfBounds {
|
||||
ptr_kind
|
||||
},
|
||||
// This cannot happen during const-eval (because interning already detects
|
||||
// dangling pointers), but it can happen in Miri.
|
||||
PointerUseAfterFree(..) =>
|
||||
{ "a dangling {kind} (use-after-free)" },
|
||||
PointerUseAfterFree(..) => DanglingPtrUseAfterFree {
|
||||
ptr_kind,
|
||||
},
|
||||
);
|
||||
// Do not allow pointers to uninhabited types.
|
||||
if place.layout.abi.is_uninhabited() {
|
||||
throw_validation_failure!(self.path,
|
||||
{ "a {kind} pointing to uninhabited type {}", place.layout.ty }
|
||||
)
|
||||
let ty = place.layout.ty;
|
||||
throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
|
||||
}
|
||||
// Recursive checking
|
||||
if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
|
||||
|
@ -441,9 +419,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
// this check is so important.
|
||||
// This check is reachable when the const just referenced the static,
|
||||
// but never read it (so we never entered `before_access_global`).
|
||||
throw_validation_failure!(self.path,
|
||||
{ "a {} pointing to a static variable in a constant", kind }
|
||||
);
|
||||
throw_validation_failure!(self.path, PtrToStatic { ptr_kind });
|
||||
}
|
||||
// We skip recursively checking other statics. These statics must be sound by
|
||||
// themselves, and the only way to get broken statics here is by using
|
||||
|
@ -464,9 +440,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
// This should be unreachable, but if someone manages to copy a pointer
|
||||
// out of a `static`, then that pointer might point to mutable memory,
|
||||
// and we would catch that here.
|
||||
throw_validation_failure!(self.path,
|
||||
{ "a {} pointing to mutable memory in a constant", kind }
|
||||
);
|
||||
throw_validation_failure!(self.path, PtrToMut { ptr_kind });
|
||||
}
|
||||
}
|
||||
// Nothing to check for these.
|
||||
|
@ -496,22 +470,24 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
let ty = value.layout.ty;
|
||||
match ty.kind() {
|
||||
ty::Bool => {
|
||||
let value = self.read_scalar(value, "a boolean")?;
|
||||
let value = self.read_scalar(value, ExpectedKind::Bool)?;
|
||||
try_validation!(
|
||||
value.to_bool(),
|
||||
self.path,
|
||||
InvalidBool(..) =>
|
||||
{ "{:x}", value } expected { "a boolean" },
|
||||
InvalidBool(..) => ValidationErrorKind::InvalidBool {
|
||||
value: format!("{value:x}"),
|
||||
}
|
||||
);
|
||||
Ok(true)
|
||||
}
|
||||
ty::Char => {
|
||||
let value = self.read_scalar(value, "a unicode scalar value")?;
|
||||
let value = self.read_scalar(value, ExpectedKind::Char)?;
|
||||
try_validation!(
|
||||
value.to_char(),
|
||||
self.path,
|
||||
InvalidChar(..) =>
|
||||
{ "{:x}", value } expected { "a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)" },
|
||||
InvalidChar(..) => ValidationErrorKind::InvalidChar {
|
||||
value: format!("{value:x}"),
|
||||
}
|
||||
);
|
||||
Ok(true)
|
||||
}
|
||||
|
@ -521,16 +497,17 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
let value = self.read_scalar(
|
||||
value,
|
||||
if matches!(ty.kind(), ty::Float(..)) {
|
||||
"a floating point number"
|
||||
ExpectedKind::Float
|
||||
} else {
|
||||
"an integer"
|
||||
ExpectedKind::Int
|
||||
},
|
||||
)?;
|
||||
// As a special exception we *do* match on a `Scalar` here, since we truly want
|
||||
// to know its underlying representation (and *not* cast it to an integer).
|
||||
if matches!(value, Scalar::Ptr(..)) {
|
||||
throw_validation_failure!(self.path,
|
||||
{ "{:x}", value } expected { "plain (non-pointer) bytes" }
|
||||
throw_validation_failure!(
|
||||
self.path,
|
||||
ExpectedNonPtr { value: format!("{value:x}") }
|
||||
)
|
||||
}
|
||||
Ok(true)
|
||||
|
@ -540,7 +517,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
// actually enforce the strict rules for raw pointers (mostly because
|
||||
// that lets us re-use `ref_to_mplace`).
|
||||
let place =
|
||||
self.ecx.ref_to_mplace(&self.read_immediate(value, "a raw pointer")?)?;
|
||||
self.ecx.ref_to_mplace(&self.read_immediate(value, ExpectedKind::RawPtr)?)?;
|
||||
if place.layout.is_unsized() {
|
||||
self.check_wide_ptr_meta(place.meta, place.layout)?;
|
||||
}
|
||||
|
@ -554,14 +531,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
// a ZST).
|
||||
let layout = self.ecx.layout_of(*ty)?;
|
||||
if !layout.is_zst() {
|
||||
throw_validation_failure!(self.path, { "mutable reference in a `const`" });
|
||||
throw_validation_failure!(self.path, MutableRefInConst);
|
||||
}
|
||||
}
|
||||
self.check_safe_pointer(value, "reference")?;
|
||||
self.check_safe_pointer(value, PointerKind::Ref)?;
|
||||
Ok(true)
|
||||
}
|
||||
ty::FnPtr(_sig) => {
|
||||
let value = self.read_scalar(value, "a function pointer")?;
|
||||
let value = self.read_scalar(value, ExpectedKind::FnPtr)?;
|
||||
|
||||
// If we check references recursively, also check that this points to a function.
|
||||
if let Some(_) = self.ref_tracking {
|
||||
|
@ -570,19 +547,20 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
self.ecx.get_ptr_fn(ptr),
|
||||
self.path,
|
||||
DanglingIntPointer(..) |
|
||||
InvalidFunctionPointer(..) =>
|
||||
{ "{ptr}" } expected { "a function pointer" },
|
||||
InvalidFunctionPointer(..) => InvalidFnPtr {
|
||||
value: format!("{ptr}"),
|
||||
},
|
||||
);
|
||||
// FIXME: Check if the signature matches
|
||||
} else {
|
||||
// Otherwise (for standalone Miri), we have to still check it to be non-null.
|
||||
if self.ecx.scalar_may_be_null(value)? {
|
||||
throw_validation_failure!(self.path, { "a null function pointer" });
|
||||
throw_validation_failure!(self.path, NullFnPtr);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
ty::Never => throw_validation_failure!(self.path, { "a value of the never type `!`" }),
|
||||
ty::Never => throw_validation_failure!(self.path, NeverVal),
|
||||
ty::Foreign(..) | ty::FnDef(..) => {
|
||||
// Nothing to check.
|
||||
Ok(true)
|
||||
|
@ -629,12 +607,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
if start == 1 && end == max_value {
|
||||
// Only null is the niche. So make sure the ptr is NOT null.
|
||||
if self.ecx.scalar_may_be_null(scalar)? {
|
||||
throw_validation_failure!(self.path,
|
||||
{ "a potentially null pointer" }
|
||||
expected {
|
||||
"something that cannot possibly fail to be {}",
|
||||
wrapping_range_format(valid_range, max_value)
|
||||
}
|
||||
throw_validation_failure!(
|
||||
self.path,
|
||||
NullablePtrOutOfRange { range: valid_range, max_value }
|
||||
)
|
||||
} else {
|
||||
return Ok(());
|
||||
|
@ -645,12 +620,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
} else {
|
||||
// Conservatively, we reject, because the pointer *could* have a bad
|
||||
// value.
|
||||
throw_validation_failure!(self.path,
|
||||
{ "a pointer" }
|
||||
expected {
|
||||
"something that cannot possibly fail to be {}",
|
||||
wrapping_range_format(valid_range, max_value)
|
||||
}
|
||||
throw_validation_failure!(
|
||||
self.path,
|
||||
PtrOutOfRange { range: valid_range, max_value }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -659,9 +631,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
if valid_range.contains(bits) {
|
||||
Ok(())
|
||||
} else {
|
||||
throw_validation_failure!(self.path,
|
||||
{ "{}", bits }
|
||||
expected { "something {}", wrapping_range_format(valid_range, max_value) }
|
||||
throw_validation_failure!(
|
||||
self.path,
|
||||
OutOfRange { value: format!("{bits}"), range: valid_range, max_value }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -685,10 +657,11 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
Ok(try_validation!(
|
||||
this.ecx.read_discriminant(op),
|
||||
this.path,
|
||||
InvalidTag(val) =>
|
||||
{ "{:x}", val } expected { "a valid enum tag" },
|
||||
InvalidUninitBytes(None) =>
|
||||
{ "uninitialized bytes" } expected { "a valid enum tag" },
|
||||
InvalidTag(val) => InvalidEnumTag {
|
||||
value: format!("{val:x}"),
|
||||
},
|
||||
|
||||
InvalidUninitBytes(None) => UninitEnumTag,
|
||||
)
|
||||
.1)
|
||||
})
|
||||
|
@ -730,7 +703,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
// Special check preventing `UnsafeCell` inside unions in the inner part of constants.
|
||||
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) {
|
||||
if !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) {
|
||||
throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
|
||||
throw_validation_failure!(self.path, UnsafeCell);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -738,7 +711,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
|
||||
#[inline]
|
||||
fn visit_box(&mut self, op: &OpTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
|
||||
self.check_safe_pointer(op, "box")?;
|
||||
self.check_safe_pointer(op, PointerKind::Box)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -756,7 +729,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. }))
|
||||
&& def.is_unsafe_cell()
|
||||
{
|
||||
throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
|
||||
throw_validation_failure!(self.path, UnsafeCell);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -775,14 +748,13 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
// MyNewtype and then the scalar in there).
|
||||
match op.layout.abi {
|
||||
Abi::Uninhabited => {
|
||||
throw_validation_failure!(self.path,
|
||||
{ "a value of uninhabited type {:?}", op.layout.ty }
|
||||
);
|
||||
let ty = op.layout.ty;
|
||||
throw_validation_failure!(self.path, UninhabitedVal { ty });
|
||||
}
|
||||
Abi::Scalar(scalar_layout) => {
|
||||
if !scalar_layout.is_uninit_valid() {
|
||||
// There is something to check here.
|
||||
let scalar = self.read_scalar(op, "initialized scalar value")?;
|
||||
let scalar = self.read_scalar(op, ExpectedKind::InitScalar)?;
|
||||
self.visit_scalar(scalar, scalar_layout)?;
|
||||
}
|
||||
}
|
||||
|
@ -792,7 +764,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
// the other must be init.
|
||||
if !a_layout.is_uninit_valid() && !b_layout.is_uninit_valid() {
|
||||
let (a, b) =
|
||||
self.read_immediate(op, "initialized scalar value")?.to_scalar_pair();
|
||||
self.read_immediate(op, ExpectedKind::InitScalar)?.to_scalar_pair();
|
||||
self.visit_scalar(a, a_layout)?;
|
||||
self.visit_scalar(b, b_layout)?;
|
||||
}
|
||||
|
@ -822,7 +794,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
try_validation!(
|
||||
self.ecx.read_bytes_ptr_strip_provenance(mplace.ptr, Size::from_bytes(len)),
|
||||
self.path,
|
||||
InvalidUninitBytes(..) => { "uninitialized data in `str`" },
|
||||
InvalidUninitBytes(..) => { UninitStr },
|
||||
);
|
||||
}
|
||||
ty::Array(tys, ..) | ty::Slice(tys)
|
||||
|
@ -852,7 +824,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
Left(mplace) => mplace,
|
||||
Right(imm) => match *imm {
|
||||
Immediate::Uninit =>
|
||||
throw_validation_failure!(self.path, { "uninitialized bytes" }),
|
||||
throw_validation_failure!(self.path, UninitVal),
|
||||
Immediate::Scalar(..) | Immediate::ScalarPair(..) =>
|
||||
bug!("arrays/slices can never have Scalar/ScalarPair layout"),
|
||||
}
|
||||
|
@ -888,7 +860,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
.unwrap();
|
||||
self.path.push(PathElem::ArrayElem(i));
|
||||
|
||||
throw_validation_failure!(self.path, { "uninitialized bytes" })
|
||||
throw_validation_failure!(self.path, UninitVal)
|
||||
}
|
||||
|
||||
// Propagate upwards (that will also check for unexpected errors).
|
||||
|
@ -929,12 +901,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
match visitor.visit_value(&op) {
|
||||
Ok(()) => Ok(()),
|
||||
// Pass through validation failures.
|
||||
Err(err) if matches!(err.kind(), err_ub!(ValidationFailure { .. })) => Err(err),
|
||||
Err(err) if matches!(err.kind(), err_ub!(Validation { .. })) => Err(err),
|
||||
// Complain about any other kind of UB error -- those are bad because we'd like to
|
||||
// report them in a way that shows *where* in the value the issue lies.
|
||||
Err(err) if matches!(err.kind(), InterpError::UndefinedBehavior(_)) => {
|
||||
err.print_backtrace();
|
||||
bug!("Unexpected Undefined Behavior error during validation: {}", err);
|
||||
let (err, backtrace) = err.into_parts();
|
||||
backtrace.print_backtrace();
|
||||
bug!("Unexpected Undefined Behavior error during validation: {err:?}");
|
||||
}
|
||||
// Pass through everything else.
|
||||
Err(err) => Err(err),
|
||||
|
|
|
@ -4,6 +4,7 @@ Rust MIR: a lowered representation of Rust.
|
|||
|
||||
*/
|
||||
|
||||
#![deny(rustc::untranslatable_diagnostic)]
|
||||
#![feature(assert_matches)]
|
||||
#![feature(box_patterns)]
|
||||
#![feature(decl_macro)]
|
||||
|
@ -33,6 +34,8 @@ pub mod interpret;
|
|||
pub mod transform;
|
||||
pub mod util;
|
||||
|
||||
pub use errors::ReportErrorExt;
|
||||
|
||||
use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage};
|
||||
use rustc_fluent_macro::fluent_messages;
|
||||
use rustc_middle::query::Providers;
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
use hir::def_id::LocalDefId;
|
||||
use hir::{ConstContext, LangItem};
|
||||
use rustc_errors::{
|
||||
error_code, struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed,
|
||||
};
|
||||
use rustc_errors::{error_code, DiagnosticBuilder, ErrorGuaranteed};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
|
@ -152,7 +150,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
|
|||
|
||||
if let Ok(Some(ImplSource::UserDefined(data))) = implsrc {
|
||||
let span = tcx.def_span(data.impl_def_id);
|
||||
err.span_note(span, "impl defined here, but it is not `const`");
|
||||
err.subdiagnostic(errors::NonConstImplNote { span });
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -166,26 +164,30 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
|
|||
let mut err = match call_kind {
|
||||
CallKind::Normal { desugaring: Some((kind, self_ty)), .. } => {
|
||||
macro_rules! error {
|
||||
($fmt:literal) => {
|
||||
struct_span_err!(tcx.sess, span, E0015, $fmt, self_ty, ccx.const_kind())
|
||||
($err:ident) => {
|
||||
tcx.sess.create_err(errors::$err {
|
||||
span,
|
||||
ty: self_ty,
|
||||
kind: ccx.const_kind(),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
let mut err = match kind {
|
||||
CallDesugaringKind::ForLoopIntoIter => {
|
||||
error!("cannot convert `{}` into an iterator in {}s")
|
||||
error!(NonConstForLoopIntoIter)
|
||||
}
|
||||
CallDesugaringKind::QuestionBranch => {
|
||||
error!("`?` cannot determine the branch of `{}` in {}s")
|
||||
error!(NonConstQuestionBranch)
|
||||
}
|
||||
CallDesugaringKind::QuestionFromResidual => {
|
||||
error!("`?` cannot convert from residual of `{}` in {}s")
|
||||
error!(NonConstQuestionFromResidual)
|
||||
}
|
||||
CallDesugaringKind::TryBlockFromOutput => {
|
||||
error!("`try` block cannot convert `{}` to the result in {}s")
|
||||
error!(NonConstTryBlockFromOutput)
|
||||
}
|
||||
CallDesugaringKind::Await => {
|
||||
error!("cannot convert `{}` into a future in {}s")
|
||||
error!(NonConstAwait)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -193,49 +195,31 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
|
|||
err
|
||||
}
|
||||
CallKind::FnCall { fn_trait_id, self_ty } => {
|
||||
let mut err = struct_span_err!(
|
||||
tcx.sess,
|
||||
span,
|
||||
E0015,
|
||||
"cannot call non-const closure in {}s",
|
||||
ccx.const_kind(),
|
||||
);
|
||||
|
||||
match self_ty.kind() {
|
||||
let note = match self_ty.kind() {
|
||||
FnDef(def_id, ..) => {
|
||||
let span = tcx.def_span(*def_id);
|
||||
if ccx.tcx.is_const_fn_raw(*def_id) {
|
||||
span_bug!(span, "calling const FnDef errored when it shouldn't");
|
||||
}
|
||||
|
||||
err.span_note(span, "function defined here, but it is not `const`");
|
||||
Some(errors::NonConstClosureNote::FnDef { span })
|
||||
}
|
||||
FnPtr(..) => {
|
||||
err.note(format!(
|
||||
"function pointers need an RFC before allowed to be called in {}s",
|
||||
ccx.const_kind()
|
||||
));
|
||||
}
|
||||
Closure(..) => {
|
||||
err.note(format!(
|
||||
"closures need an RFC before allowed to be called in {}s",
|
||||
ccx.const_kind()
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
FnPtr(..) => Some(errors::NonConstClosureNote::FnPtr),
|
||||
Closure(..) => Some(errors::NonConstClosureNote::Closure),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut err = tcx.sess.create_err(errors::NonConstClosure {
|
||||
span,
|
||||
kind: ccx.const_kind(),
|
||||
note,
|
||||
});
|
||||
|
||||
diag_trait(&mut err, self_ty, fn_trait_id);
|
||||
err
|
||||
}
|
||||
CallKind::Operator { trait_id, self_ty, .. } => {
|
||||
let mut err = struct_span_err!(
|
||||
tcx.sess,
|
||||
span,
|
||||
E0015,
|
||||
"cannot call non-const operator in {}s",
|
||||
ccx.const_kind()
|
||||
);
|
||||
let mut sugg = None;
|
||||
|
||||
if Some(trait_id) == ccx.tcx.lang_items().eq_trait() {
|
||||
match (substs[0].unpack(), substs[1].unpack()) {
|
||||
|
@ -260,14 +244,11 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
|
|||
let rhs_pos =
|
||||
span.lo() + BytePos::from_usize(eq_idx + 2 + rhs_idx);
|
||||
let rhs_span = span.with_lo(rhs_pos).with_hi(rhs_pos);
|
||||
err.multipart_suggestion(
|
||||
"consider dereferencing here",
|
||||
vec![
|
||||
(span.shrink_to_lo(), deref.clone()),
|
||||
(rhs_span, deref),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
sugg = Some(errors::ConsiderDereferencing {
|
||||
deref,
|
||||
span: span.shrink_to_lo(),
|
||||
rhs_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,26 +256,29 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut err = tcx.sess.create_err(errors::NonConstOperator {
|
||||
span,
|
||||
kind: ccx.const_kind(),
|
||||
sugg,
|
||||
});
|
||||
diag_trait(&mut err, self_ty, trait_id);
|
||||
err
|
||||
}
|
||||
CallKind::DerefCoercion { deref_target, deref_target_ty, self_ty } => {
|
||||
let mut err = struct_span_err!(
|
||||
tcx.sess,
|
||||
span,
|
||||
E0015,
|
||||
"cannot perform deref coercion on `{}` in {}s",
|
||||
self_ty,
|
||||
ccx.const_kind()
|
||||
);
|
||||
|
||||
err.note(format!("attempting to deref into `{}`", deref_target_ty));
|
||||
|
||||
// Check first whether the source is accessible (issue #87060)
|
||||
if tcx.sess.source_map().is_span_accessible(deref_target) {
|
||||
err.span_note(deref_target, "deref defined here");
|
||||
}
|
||||
let target = if tcx.sess.source_map().is_span_accessible(deref_target) {
|
||||
Some(deref_target)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut err = tcx.sess.create_err(errors::NonConstDerefCoercion {
|
||||
span,
|
||||
ty: self_ty,
|
||||
kind: ccx.const_kind(),
|
||||
target_ty: deref_target_ty,
|
||||
deref_target: target,
|
||||
});
|
||||
|
||||
diag_trait(&mut err, self_ty, tcx.require_lang_item(LangItem::Deref, Some(span)));
|
||||
err
|
||||
|
@ -432,21 +416,12 @@ impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> {
|
|||
ccx: &ConstCx<'_, 'tcx>,
|
||||
span: Span,
|
||||
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
|
||||
let mut err = struct_span_err!(
|
||||
ccx.tcx.sess,
|
||||
ccx.tcx.sess.create_err(errors::LiveDrop {
|
||||
span,
|
||||
E0493,
|
||||
"destructor of `{}` cannot be evaluated at compile-time",
|
||||
self.dropped_ty,
|
||||
);
|
||||
err.span_label(
|
||||
span,
|
||||
format!("the destructor for this type cannot be evaluated in {}s", ccx.const_kind()),
|
||||
);
|
||||
if let Some(span) = self.dropped_at {
|
||||
err.span_label(span, "value is dropped here");
|
||||
}
|
||||
err
|
||||
dropped_ty: self.dropped_ty,
|
||||
kind: ccx.const_kind(),
|
||||
dropped_at: self.dropped_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue