avoid thread-local var indirection for non-halting diagnostics
This commit is contained in:
parent
4b9463c5b7
commit
3cb27f584b
9 changed files with 133 additions and 200 deletions
|
@ -544,7 +544,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
|
||||||
validate,
|
validate,
|
||||||
)?;
|
)?;
|
||||||
if global.track_outdated_loads && recency == LoadRecency::Outdated {
|
if global.track_outdated_loads && recency == LoadRecency::Outdated {
|
||||||
register_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad);
|
this.emit_diagnostic(NonHaltingDiagnostic::WeakMemoryOutdatedLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(loaded);
|
return Ok(loaded);
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty::TyCtxt;
|
||||||
use rustc_span::{source_map::DUMMY_SP, Span, SpanData, Symbol};
|
use rustc_span::{source_map::DUMMY_SP, SpanData, Symbol};
|
||||||
use rustc_target::abi::{Align, Size};
|
use rustc_target::abi::{Align, Size};
|
||||||
|
|
||||||
use crate::stacked_borrows::{diagnostics::TagHistory, AccessKind};
|
use crate::stacked_borrows::{diagnostics::TagHistory, AccessKind};
|
||||||
|
@ -89,15 +88,16 @@ enum DiagLevel {
|
||||||
/// Attempts to prune a stacktrace to omit the Rust runtime, and returns a bool indicating if any
|
/// Attempts to prune a stacktrace to omit the Rust runtime, and returns a bool indicating if any
|
||||||
/// frames were pruned. If the stacktrace does not have any local frames, we conclude that it must
|
/// frames were pruned. If the stacktrace does not have any local frames, we conclude that it must
|
||||||
/// be pointing to a problem in the Rust runtime itself, and do not prune it at all.
|
/// be pointing to a problem in the Rust runtime itself, and do not prune it at all.
|
||||||
fn prune_stacktrace<'mir, 'tcx>(
|
fn prune_stacktrace<'tcx>(
|
||||||
ecx: &InterpCx<'mir, 'tcx, Evaluator<'mir, 'tcx>>,
|
|
||||||
mut stacktrace: Vec<FrameInfo<'tcx>>,
|
mut stacktrace: Vec<FrameInfo<'tcx>>,
|
||||||
|
machine: &Evaluator<'_, 'tcx>,
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
) -> (Vec<FrameInfo<'tcx>>, bool) {
|
) -> (Vec<FrameInfo<'tcx>>, bool) {
|
||||||
match ecx.machine.backtrace_style {
|
match machine.backtrace_style {
|
||||||
BacktraceStyle::Off => {
|
BacktraceStyle::Off => {
|
||||||
// Remove all frames marked with `caller_location` -- that attribute indicates we
|
// Remove all frames marked with `caller_location` -- that attribute indicates we
|
||||||
// usually want to point at the caller, not them.
|
// usually want to point at the caller, not them.
|
||||||
stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
|
stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(tcx));
|
||||||
// Retain one frame so that we can print a span for the error itself
|
// Retain one frame so that we can print a span for the error itself
|
||||||
stacktrace.truncate(1);
|
stacktrace.truncate(1);
|
||||||
(stacktrace, false)
|
(stacktrace, false)
|
||||||
|
@ -107,11 +107,11 @@ fn prune_stacktrace<'mir, 'tcx>(
|
||||||
// Only prune frames if there is at least one local frame. This check ensures that if
|
// Only prune frames if there is at least one local frame. This check ensures that if
|
||||||
// we get a backtrace that never makes it to the user code because it has detected a
|
// we get a backtrace that never makes it to the user code because it has detected a
|
||||||
// bug in the Rust runtime, we don't prune away every frame.
|
// bug in the Rust runtime, we don't prune away every frame.
|
||||||
let has_local_frame = stacktrace.iter().any(|frame| ecx.machine.is_local(frame));
|
let has_local_frame = stacktrace.iter().any(|frame| machine.is_local(frame));
|
||||||
if has_local_frame {
|
if has_local_frame {
|
||||||
// Remove all frames marked with `caller_location` -- that attribute indicates we
|
// Remove all frames marked with `caller_location` -- that attribute indicates we
|
||||||
// usually want to point at the caller, not them.
|
// usually want to point at the caller, not them.
|
||||||
stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
|
stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(tcx));
|
||||||
|
|
||||||
// This is part of the logic that `std` uses to select the relevant part of a
|
// This is part of the logic that `std` uses to select the relevant part of a
|
||||||
// backtrace. But here, we only look for __rust_begin_short_backtrace, not
|
// backtrace. But here, we only look for __rust_begin_short_backtrace, not
|
||||||
|
@ -121,7 +121,7 @@ fn prune_stacktrace<'mir, 'tcx>(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.take_while(|frame| {
|
.take_while(|frame| {
|
||||||
let def_id = frame.instance.def_id();
|
let def_id = frame.instance.def_id();
|
||||||
let path = ecx.tcx.tcx.def_path_str(def_id);
|
let path = tcx.def_path_str(def_id);
|
||||||
!path.contains("__rust_begin_short_backtrace")
|
!path.contains("__rust_begin_short_backtrace")
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
@ -132,7 +132,7 @@ fn prune_stacktrace<'mir, 'tcx>(
|
||||||
// This len check ensures that we don't somehow remove every frame, as doing so breaks
|
// This len check ensures that we don't somehow remove every frame, as doing so breaks
|
||||||
// the primary error message.
|
// the primary error message.
|
||||||
while stacktrace.len() > 1
|
while stacktrace.len() > 1
|
||||||
&& stacktrace.last().map_or(false, |frame| !ecx.machine.is_local(frame))
|
&& stacktrace.last().map_or(false, |frame| !machine.is_local(frame))
|
||||||
{
|
{
|
||||||
stacktrace.pop();
|
stacktrace.pop();
|
||||||
}
|
}
|
||||||
|
@ -256,17 +256,18 @@ pub fn report_error<'tcx, 'mir>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let stacktrace = ecx.generate_stacktrace();
|
let stacktrace = ecx.generate_stacktrace();
|
||||||
let (stacktrace, was_pruned) = prune_stacktrace(ecx, stacktrace);
|
let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine, *ecx.tcx);
|
||||||
e.print_backtrace();
|
e.print_backtrace();
|
||||||
msg.insert(0, e.to_string());
|
msg.insert(0, e.to_string());
|
||||||
report_msg(
|
report_msg(
|
||||||
ecx,
|
|
||||||
DiagLevel::Error,
|
DiagLevel::Error,
|
||||||
&if let Some(title) = title { format!("{}: {}", title, msg[0]) } else { msg[0].clone() },
|
&if let Some(title) = title { format!("{}: {}", title, msg[0]) } else { msg[0].clone() },
|
||||||
msg,
|
msg,
|
||||||
vec![],
|
vec![],
|
||||||
helps,
|
helps,
|
||||||
&stacktrace,
|
&stacktrace,
|
||||||
|
&ecx.machine,
|
||||||
|
*ecx.tcx,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Include a note like `std` does when we omit frames from a backtrace
|
// Include a note like `std` does when we omit frames from a backtrace
|
||||||
|
@ -306,21 +307,21 @@ pub fn report_error<'tcx, 'mir>(
|
||||||
/// We want to present a multi-line span message for some errors. Diagnostics do not support this
|
/// We want to present a multi-line span message for some errors. Diagnostics do not support this
|
||||||
/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
|
/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
|
||||||
/// additional `span_label` or `note` call.
|
/// additional `span_label` or `note` call.
|
||||||
fn report_msg<'mir, 'tcx>(
|
fn report_msg<'tcx>(
|
||||||
ecx: &InterpCx<'mir, 'tcx, Evaluator<'mir, 'tcx>>,
|
|
||||||
diag_level: DiagLevel,
|
diag_level: DiagLevel,
|
||||||
title: &str,
|
title: &str,
|
||||||
span_msg: Vec<String>,
|
span_msg: Vec<String>,
|
||||||
notes: Vec<(Option<SpanData>, String)>,
|
notes: Vec<(Option<SpanData>, String)>,
|
||||||
helps: Vec<(Option<SpanData>, String)>,
|
helps: Vec<(Option<SpanData>, String)>,
|
||||||
stacktrace: &[FrameInfo<'tcx>],
|
stacktrace: &[FrameInfo<'tcx>],
|
||||||
|
machine: &Evaluator<'_, 'tcx>,
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
) {
|
) {
|
||||||
let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
|
let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
|
||||||
let sess = ecx.tcx.sess;
|
|
||||||
let mut err = match diag_level {
|
let mut err = match diag_level {
|
||||||
DiagLevel::Error => sess.struct_span_err(span, title).forget_guarantee(),
|
DiagLevel::Error => tcx.sess.struct_span_err(span, title).forget_guarantee(),
|
||||||
DiagLevel::Warning => sess.struct_span_warn(span, title),
|
DiagLevel::Warning => tcx.sess.struct_span_warn(span, title),
|
||||||
DiagLevel::Note => sess.diagnostic().span_note_diag(span, title),
|
DiagLevel::Note => tcx.sess.diagnostic().span_note_diag(span, title),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show main message.
|
// Show main message.
|
||||||
|
@ -357,7 +358,7 @@ fn report_msg<'mir, 'tcx>(
|
||||||
}
|
}
|
||||||
// Add backtrace
|
// Add backtrace
|
||||||
for (idx, frame_info) in stacktrace.iter().enumerate() {
|
for (idx, frame_info) in stacktrace.iter().enumerate() {
|
||||||
let is_local = ecx.machine.is_local(frame_info);
|
let is_local = machine.is_local(frame_info);
|
||||||
// No span for non-local frames and the first frame (which is the error site).
|
// No span for non-local frames and the first frame (which is the error site).
|
||||||
if is_local && idx > 0 {
|
if is_local && idx > 0 {
|
||||||
err.span_note(frame_info.span, &frame_info.to_string());
|
err.span_note(frame_info.span, &frame_info.to_string());
|
||||||
|
@ -369,164 +370,95 @@ fn report_msg<'mir, 'tcx>(
|
||||||
err.emit();
|
err.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
pub fn emit_diagnostic<'tcx>(e: NonHaltingDiagnostic, machine: &Evaluator<'_, 'tcx>, tcx: TyCtxt<'tcx>) {
|
||||||
static DIAGNOSTICS: RefCell<Vec<NonHaltingDiagnostic>> = RefCell::new(Vec::new());
|
use NonHaltingDiagnostic::*;
|
||||||
}
|
|
||||||
|
|
||||||
/// Schedule a diagnostic for emitting. This function works even if you have no `InterpCx` available.
|
let stacktrace = MiriEvalContext::generate_stacktrace_from_stack(machine.threads.active_thread_stack());
|
||||||
/// The diagnostic will be emitted after the current interpreter step is finished.
|
let (stacktrace, _was_pruned) = prune_stacktrace(stacktrace, machine, tcx);
|
||||||
pub fn register_diagnostic(e: NonHaltingDiagnostic) {
|
|
||||||
DIAGNOSTICS.with(|diagnostics| diagnostics.borrow_mut().push(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remember enough about the topmost frame so that we can restore the stack
|
let (title, diag_level) = match e {
|
||||||
/// after a step was taken.
|
RejectedIsolatedOp(_) =>
|
||||||
pub struct TopFrameInfo<'tcx> {
|
("operation rejected by isolation", DiagLevel::Warning),
|
||||||
stack_size: usize,
|
Int2Ptr { .. } => ("integer-to-pointer cast", DiagLevel::Warning),
|
||||||
instance: Option<ty::Instance<'tcx>>,
|
CreatedPointerTag(..)
|
||||||
span: Span,
|
| PoppedPointerTag(..)
|
||||||
|
| CreatedCallId(..)
|
||||||
|
| CreatedAlloc(..)
|
||||||
|
| FreedAlloc(..)
|
||||||
|
| ProgressReport { .. }
|
||||||
|
| WeakMemoryOutdatedLoad =>
|
||||||
|
("tracking was triggered", DiagLevel::Note),
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg = match e {
|
||||||
|
CreatedPointerTag(tag, None) =>
|
||||||
|
format!("created tag {tag:?}"),
|
||||||
|
CreatedPointerTag(tag, Some((alloc_id, range))) =>
|
||||||
|
format!("created tag {tag:?} at {alloc_id:?}{range:?}"),
|
||||||
|
PoppedPointerTag(item, tag) =>
|
||||||
|
match tag {
|
||||||
|
None =>
|
||||||
|
format!(
|
||||||
|
"popped tracked tag for item {item:?} due to deallocation",
|
||||||
|
),
|
||||||
|
Some((tag, access)) => {
|
||||||
|
format!(
|
||||||
|
"popped tracked tag for item {item:?} due to {access:?} access for {tag:?}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CreatedCallId(id) =>
|
||||||
|
format!("function call with id {id}"),
|
||||||
|
CreatedAlloc(AllocId(id), size, align, kind) =>
|
||||||
|
format!(
|
||||||
|
"created {kind} allocation of {size} bytes (alignment {align} bytes) with id {id}",
|
||||||
|
size = size.bytes(),
|
||||||
|
align = align.bytes(),
|
||||||
|
),
|
||||||
|
FreedAlloc(AllocId(id)) =>
|
||||||
|
format!("freed allocation with id {id}"),
|
||||||
|
RejectedIsolatedOp(ref op) =>
|
||||||
|
format!("{op} was made to return an error due to isolation"),
|
||||||
|
ProgressReport { .. } =>
|
||||||
|
format!("progress report: current operation being executed is here"),
|
||||||
|
Int2Ptr { .. } =>
|
||||||
|
format!("integer-to-pointer cast"),
|
||||||
|
WeakMemoryOutdatedLoad =>
|
||||||
|
format!("weak memory emulation: outdated value returned from load"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let notes = match e {
|
||||||
|
ProgressReport { block_count } => {
|
||||||
|
// It is important that each progress report is slightly different, since
|
||||||
|
// identical diagnostics are being deduplicated.
|
||||||
|
vec![
|
||||||
|
(None, format!("so far, {block_count} basic blocks have been executed")),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let helps = match e {
|
||||||
|
Int2Ptr { details: true } =>
|
||||||
|
vec![
|
||||||
|
(None, format!("This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,")),
|
||||||
|
(None, format!("which means that Miri might miss pointer bugs in this program.")),
|
||||||
|
(None, format!("See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation.")),
|
||||||
|
(None, format!("To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead.")),
|
||||||
|
(None, format!("You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics.")),
|
||||||
|
(None, format!("Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning.")),
|
||||||
|
],
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
report_msg(diag_level, title, vec![msg], notes, helps, &stacktrace, machine, tcx);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
|
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
|
||||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
|
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
|
||||||
fn preprocess_diagnostics(&self) -> TopFrameInfo<'tcx> {
|
fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
|
||||||
// Ensure we have no lingering diagnostics.
|
|
||||||
DIAGNOSTICS.with(|diagnostics| assert!(diagnostics.borrow().is_empty()));
|
|
||||||
|
|
||||||
let this = self.eval_context_ref();
|
let this = self.eval_context_ref();
|
||||||
if this.active_thread_stack().is_empty() {
|
emit_diagnostic(e, &this.machine, *this.tcx);
|
||||||
// Diagnostics can happen even with the empty stack (e.g. deallocation of thread-local statics).
|
|
||||||
return TopFrameInfo { stack_size: 0, instance: None, span: DUMMY_SP };
|
|
||||||
}
|
|
||||||
let frame = this.frame();
|
|
||||||
|
|
||||||
TopFrameInfo {
|
|
||||||
stack_size: this.active_thread_stack().len(),
|
|
||||||
instance: Some(frame.instance),
|
|
||||||
span: frame.current_span(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emit all diagnostics that were registed with `register_diagnostics`
|
|
||||||
fn process_diagnostics(&self, info: TopFrameInfo<'tcx>) {
|
|
||||||
let this = self.eval_context_ref();
|
|
||||||
DIAGNOSTICS.with(|diagnostics| {
|
|
||||||
let mut diagnostics = diagnostics.borrow_mut();
|
|
||||||
if diagnostics.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// We need to fix up the stack trace, because the machine has already
|
|
||||||
// stepped to the next statement.
|
|
||||||
let mut stacktrace = this.generate_stacktrace();
|
|
||||||
// Remove newly pushed frames.
|
|
||||||
while stacktrace.len() > info.stack_size {
|
|
||||||
stacktrace.remove(0);
|
|
||||||
}
|
|
||||||
// Add popped frame back.
|
|
||||||
if stacktrace.len() < info.stack_size {
|
|
||||||
assert!(
|
|
||||||
stacktrace.len() == info.stack_size - 1,
|
|
||||||
"we should never pop more than one frame at once"
|
|
||||||
);
|
|
||||||
let frame_info = FrameInfo {
|
|
||||||
instance: info.instance.unwrap(),
|
|
||||||
span: info.span,
|
|
||||||
lint_root: None,
|
|
||||||
};
|
|
||||||
stacktrace.insert(0, frame_info);
|
|
||||||
} else if let Some(instance) = info.instance {
|
|
||||||
// Adjust topmost frame.
|
|
||||||
stacktrace[0].span = info.span;
|
|
||||||
assert_eq!(
|
|
||||||
stacktrace[0].instance, instance,
|
|
||||||
"we should not pop and push a frame in one step"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (stacktrace, _was_pruned) = prune_stacktrace(this, stacktrace);
|
|
||||||
|
|
||||||
// Show diagnostics.
|
|
||||||
for e in diagnostics.drain(..) {
|
|
||||||
use NonHaltingDiagnostic::*;
|
|
||||||
|
|
||||||
let (title, diag_level) = match e {
|
|
||||||
RejectedIsolatedOp(_) =>
|
|
||||||
("operation rejected by isolation", DiagLevel::Warning),
|
|
||||||
Int2Ptr { .. } => ("integer-to-pointer cast", DiagLevel::Warning),
|
|
||||||
CreatedPointerTag(..)
|
|
||||||
| PoppedPointerTag(..)
|
|
||||||
| CreatedCallId(..)
|
|
||||||
| CreatedAlloc(..)
|
|
||||||
| FreedAlloc(..)
|
|
||||||
| ProgressReport { .. }
|
|
||||||
| WeakMemoryOutdatedLoad =>
|
|
||||||
("tracking was triggered", DiagLevel::Note),
|
|
||||||
};
|
|
||||||
|
|
||||||
let msg = match e {
|
|
||||||
CreatedPointerTag(tag, None) =>
|
|
||||||
format!("created tag {tag:?}"),
|
|
||||||
CreatedPointerTag(tag, Some((alloc_id, range))) =>
|
|
||||||
format!("created tag {tag:?} at {alloc_id:?}{range:?}"),
|
|
||||||
PoppedPointerTag(item, tag) =>
|
|
||||||
match tag {
|
|
||||||
None =>
|
|
||||||
format!(
|
|
||||||
"popped tracked tag for item {item:?} due to deallocation",
|
|
||||||
),
|
|
||||||
Some((tag, access)) => {
|
|
||||||
format!(
|
|
||||||
"popped tracked tag for item {item:?} due to {access:?} access for {tag:?}",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
CreatedCallId(id) =>
|
|
||||||
format!("function call with id {id}"),
|
|
||||||
CreatedAlloc(AllocId(id), size, align, kind) =>
|
|
||||||
format!(
|
|
||||||
"created {kind} allocation of {size} bytes (alignment {align} bytes) with id {id}",
|
|
||||||
size = size.bytes(),
|
|
||||||
align = align.bytes(),
|
|
||||||
),
|
|
||||||
FreedAlloc(AllocId(id)) =>
|
|
||||||
format!("freed allocation with id {id}"),
|
|
||||||
RejectedIsolatedOp(ref op) =>
|
|
||||||
format!("{op} was made to return an error due to isolation"),
|
|
||||||
ProgressReport { .. } =>
|
|
||||||
format!("progress report: current operation being executed is here"),
|
|
||||||
Int2Ptr { .. } =>
|
|
||||||
format!("integer-to-pointer cast"),
|
|
||||||
WeakMemoryOutdatedLoad =>
|
|
||||||
format!("weak memory emulation: outdated value returned from load"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let notes = match e {
|
|
||||||
ProgressReport { block_count } => {
|
|
||||||
// It is important that each progress report is slightly different, since
|
|
||||||
// identical diagnostics are being deduplicated.
|
|
||||||
vec![
|
|
||||||
(None, format!("so far, {block_count} basic blocks have been executed")),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
_ => vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let helps = match e {
|
|
||||||
Int2Ptr { details: true } =>
|
|
||||||
vec![
|
|
||||||
(None, format!("This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`,")),
|
|
||||||
(None, format!("which means that Miri might miss pointer bugs in this program.")),
|
|
||||||
(None, format!("See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation.")),
|
|
||||||
(None, format!("To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead.")),
|
|
||||||
(None, format!("You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics.")),
|
|
||||||
(None, format!("Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning.")),
|
|
||||||
],
|
|
||||||
_ => vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
report_msg(this, diag_level, title, vec![msg], notes, helps, &stacktrace);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We had a panic in Miri itself, try to print something useful.
|
/// We had a panic in Miri itself, try to print something useful.
|
||||||
|
@ -538,13 +470,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||||
let this = self.eval_context_ref();
|
let this = self.eval_context_ref();
|
||||||
let stacktrace = this.generate_stacktrace();
|
let stacktrace = this.generate_stacktrace();
|
||||||
report_msg(
|
report_msg(
|
||||||
this,
|
|
||||||
DiagLevel::Note,
|
DiagLevel::Note,
|
||||||
"the place in the program where the ICE was triggered",
|
"the place in the program where the ICE was triggered",
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
&stacktrace,
|
&stacktrace,
|
||||||
|
&this.machine,
|
||||||
|
*this.tcx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/eval.rs
11
src/eval.rs
|
@ -190,11 +190,6 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
|
||||||
Evaluator::new(config, layout_cx),
|
Evaluator::new(config, layout_cx),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Capture the current interpreter stack state (which should be empty) so that we can emit
|
|
||||||
// allocation-tracking and tag-tracking diagnostics for allocations which are part of the
|
|
||||||
// early runtime setup.
|
|
||||||
let info = ecx.preprocess_diagnostics();
|
|
||||||
|
|
||||||
// Some parts of initialization require a full `InterpCx`.
|
// Some parts of initialization require a full `InterpCx`.
|
||||||
Evaluator::late_init(&mut ecx, config)?;
|
Evaluator::late_init(&mut ecx, config)?;
|
||||||
|
|
||||||
|
@ -324,10 +319,6 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit any diagnostics related to the setup process for the runtime, so that when the
|
|
||||||
// interpreter loop starts there are no unprocessed diagnostics.
|
|
||||||
ecx.process_diagnostics(info);
|
|
||||||
|
|
||||||
Ok((ecx, ret_place))
|
Ok((ecx, ret_place))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +347,6 @@ pub fn eval_entry<'tcx>(
|
||||||
let res: thread::Result<InterpResult<'_, i64>> = panic::catch_unwind(AssertUnwindSafe(|| {
|
let res: thread::Result<InterpResult<'_, i64>> = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||||
// Main loop.
|
// Main loop.
|
||||||
loop {
|
loop {
|
||||||
let info = ecx.preprocess_diagnostics();
|
|
||||||
match ecx.schedule()? {
|
match ecx.schedule()? {
|
||||||
SchedulingAction::ExecuteStep => {
|
SchedulingAction::ExecuteStep => {
|
||||||
assert!(ecx.step()?, "a terminated thread was scheduled for execution");
|
assert!(ecx.step()?, "a terminated thread was scheduled for execution");
|
||||||
|
@ -374,7 +364,6 @@ pub fn eval_entry<'tcx>(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ecx.process_diagnostics(info);
|
|
||||||
}
|
}
|
||||||
let return_code = ecx.read_scalar(&ret_place.into())?.to_machine_isize(&ecx)?;
|
let return_code = ecx.read_scalar(&ret_place.into())?.to_machine_isize(&ecx)?;
|
||||||
Ok(return_code)
|
Ok(return_code)
|
||||||
|
|
|
@ -508,7 +508,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
RejectOpWith::Warning => {
|
RejectOpWith::Warning => {
|
||||||
register_diagnostic(NonHaltingDiagnostic::RejectedIsolatedOp(op_name.to_string()));
|
this.emit_diagnostic(NonHaltingDiagnostic::RejectedIsolatedOp(op_name.to_string()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
RejectOpWith::NoWarning => Ok(()), // no warning
|
RejectOpWith::NoWarning => Ok(()), // no warning
|
||||||
|
@ -881,6 +881,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
||||||
None => tcx.item_name(def_id),
|
None => tcx.item_name(def_id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn current_span(&self) -> CurrentSpan<'_, 'mir, 'tcx> {
|
||||||
|
let this = self.eval_context_ref();
|
||||||
|
CurrentSpan { current_frame_idx: None, machine: &this.machine, tcx: *this.tcx }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
|
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
|
||||||
|
@ -901,6 +906,12 @@ pub struct CurrentSpan<'a, 'mir, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'mir: 'a, 'tcx: 'a + 'mir> CurrentSpan<'a, 'mir, 'tcx> {
|
impl<'a, 'mir: 'a, 'tcx: 'a + 'mir> CurrentSpan<'a, 'mir, 'tcx> {
|
||||||
|
/// Not really about the `CurrentSpan`, but we just happen to have all the things needed to emit
|
||||||
|
/// diagnostics like that.
|
||||||
|
pub fn emit_diagnostic(&self, e: NonHaltingDiagnostic) {
|
||||||
|
emit_diagnostic(e, self.machine, self.tcx);
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the current span, skipping non-local frames.
|
/// Get the current span, skipping non-local frames.
|
||||||
/// This function is backed by a cache, and can be assumed to be very fast.
|
/// This function is backed by a cache, and can be assumed to be very fast.
|
||||||
pub fn get(&mut self) -> Span {
|
pub fn get(&mut self) -> Span {
|
||||||
|
|
|
@ -142,7 +142,7 @@ impl<'mir, 'tcx> GlobalStateInner {
|
||||||
let first = past_warnings.is_empty();
|
let first = past_warnings.is_empty();
|
||||||
if past_warnings.insert(ecx.cur_span()) {
|
if past_warnings.insert(ecx.cur_span()) {
|
||||||
// Newly inserted, so first time we see this span.
|
// Newly inserted, so first time we see this span.
|
||||||
register_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first });
|
ecx.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ pub use crate::concurrency::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
pub use crate::diagnostics::{
|
pub use crate::diagnostics::{
|
||||||
register_diagnostic, report_error, EvalContextExt as DiagnosticsEvalContextExt,
|
emit_diagnostic, report_error, EvalContextExt as DiagnosticsEvalContextExt,
|
||||||
NonHaltingDiagnostic, TerminationInfo,
|
NonHaltingDiagnostic, TerminationInfo,
|
||||||
};
|
};
|
||||||
pub use crate::eval::{
|
pub use crate::eval::{
|
||||||
|
|
|
@ -755,7 +755,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
|
||||||
) -> InterpResult<'tcx, Cow<'b, Allocation<Self::Provenance, Self::AllocExtra>>> {
|
) -> InterpResult<'tcx, Cow<'b, Allocation<Self::Provenance, Self::AllocExtra>>> {
|
||||||
let kind = kind.expect("we set our STATIC_KIND so this cannot be None");
|
let kind = kind.expect("we set our STATIC_KIND so this cannot be None");
|
||||||
if ecx.machine.tracked_alloc_ids.contains(&id) {
|
if ecx.machine.tracked_alloc_ids.contains(&id) {
|
||||||
register_diagnostic(NonHaltingDiagnostic::CreatedAlloc(
|
ecx.emit_diagnostic(NonHaltingDiagnostic::CreatedAlloc(
|
||||||
id,
|
id,
|
||||||
alloc.size(),
|
alloc.size(),
|
||||||
alloc.align,
|
alloc.align,
|
||||||
|
@ -813,7 +813,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
|
||||||
}
|
}
|
||||||
let absolute_addr = intptrcast::GlobalStateInner::rel_ptr_to_addr(ecx, ptr);
|
let absolute_addr = intptrcast::GlobalStateInner::rel_ptr_to_addr(ecx, ptr);
|
||||||
let sb_tag = if let Some(stacked_borrows) = &ecx.machine.stacked_borrows {
|
let sb_tag = if let Some(stacked_borrows) = &ecx.machine.stacked_borrows {
|
||||||
stacked_borrows.borrow_mut().base_ptr_tag(ptr.provenance)
|
stacked_borrows.borrow_mut().base_ptr_tag(ptr.provenance, &ecx.current_span())
|
||||||
} else {
|
} else {
|
||||||
// Value does not matter, SB is disabled
|
// Value does not matter, SB is disabled
|
||||||
SbTag::default()
|
SbTag::default()
|
||||||
|
@ -937,7 +937,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
|
||||||
range: AllocRange,
|
range: AllocRange,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
if machine.tracked_alloc_ids.contains(&alloc_id) {
|
if machine.tracked_alloc_ids.contains(&alloc_id) {
|
||||||
register_diagnostic(NonHaltingDiagnostic::FreedAlloc(alloc_id));
|
emit_diagnostic(NonHaltingDiagnostic::FreedAlloc(alloc_id), machine, tcx);
|
||||||
}
|
}
|
||||||
if let Some(data_race) = &mut alloc_extra.data_race {
|
if let Some(data_race) = &mut alloc_extra.data_race {
|
||||||
data_race.deallocate(
|
data_race.deallocate(
|
||||||
|
@ -993,7 +993,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
|
||||||
let stacked_borrows = ecx.machine.stacked_borrows.as_ref();
|
let stacked_borrows = ecx.machine.stacked_borrows.as_ref();
|
||||||
|
|
||||||
let extra = FrameData {
|
let extra = FrameData {
|
||||||
stacked_borrows: stacked_borrows.map(|sb| sb.borrow_mut().new_frame()),
|
stacked_borrows: stacked_borrows.map(|sb| sb.borrow_mut().new_frame(&ecx.current_span())),
|
||||||
catch_unwind: None,
|
catch_unwind: None,
|
||||||
timing,
|
timing,
|
||||||
};
|
};
|
||||||
|
@ -1018,7 +1018,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
|
||||||
// Possibly report our progress.
|
// Possibly report our progress.
|
||||||
if let Some(report_progress) = ecx.machine.report_progress {
|
if let Some(report_progress) = ecx.machine.report_progress {
|
||||||
if ecx.machine.basic_block_count % u64::from(report_progress) == 0 {
|
if ecx.machine.basic_block_count % u64::from(report_progress) == 0 {
|
||||||
register_diagnostic(NonHaltingDiagnostic::ProgressReport {
|
ecx.emit_diagnostic(NonHaltingDiagnostic::ProgressReport {
|
||||||
block_count: ecx.machine.basic_block_count,
|
block_count: ecx.machine.basic_block_count,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -471,7 +471,7 @@ impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir
|
||||||
Some((orig_tag, kind))
|
Some((orig_tag, kind))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
register_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, summary));
|
self.current_span.emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, summary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -178,11 +178,11 @@ impl GlobalStateInner {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_frame(&mut self) -> FrameExtra {
|
pub fn new_frame(&mut self, current_span: &CurrentSpan<'_, '_, '_>) -> FrameExtra {
|
||||||
let call_id = self.next_call_id;
|
let call_id = self.next_call_id;
|
||||||
trace!("new_frame: Assigning call ID {}", call_id);
|
trace!("new_frame: Assigning call ID {}", call_id);
|
||||||
if self.tracked_call_ids.contains(&call_id) {
|
if self.tracked_call_ids.contains(&call_id) {
|
||||||
register_diagnostic(NonHaltingDiagnostic::CreatedCallId(call_id));
|
current_span.emit_diagnostic(NonHaltingDiagnostic::CreatedCallId(call_id));
|
||||||
}
|
}
|
||||||
self.next_call_id = NonZeroU64::new(call_id.get() + 1).unwrap();
|
self.next_call_id = NonZeroU64::new(call_id.get() + 1).unwrap();
|
||||||
FrameExtra { call_id, protected_tags: SmallVec::new() }
|
FrameExtra { call_id, protected_tags: SmallVec::new() }
|
||||||
|
@ -199,11 +199,11 @@ impl GlobalStateInner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base_ptr_tag(&mut self, id: AllocId) -> SbTag {
|
pub fn base_ptr_tag(&mut self, id: AllocId, current_span: &CurrentSpan<'_, '_, '_>) -> SbTag {
|
||||||
self.base_ptr_tags.get(&id).copied().unwrap_or_else(|| {
|
self.base_ptr_tags.get(&id).copied().unwrap_or_else(|| {
|
||||||
let tag = self.new_ptr();
|
let tag = self.new_ptr();
|
||||||
if self.tracked_pointer_tags.contains(&tag) {
|
if self.tracked_pointer_tags.contains(&tag) {
|
||||||
register_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(tag.0, None));
|
current_span.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(tag.0, None));
|
||||||
}
|
}
|
||||||
trace!("New allocation {:?} has base tag {:?}", id, tag);
|
trace!("New allocation {:?} has base tag {:?}", id, tag);
|
||||||
self.base_ptr_tags.try_insert(id, tag).unwrap();
|
self.base_ptr_tags.try_insert(id, tag).unwrap();
|
||||||
|
@ -572,9 +572,9 @@ impl Stacks {
|
||||||
// not through a pointer). That is, whenever we directly write to a local, this will pop
|
// not through a pointer). That is, whenever we directly write to a local, this will pop
|
||||||
// everything else off the stack, invalidating all previous pointers,
|
// everything else off the stack, invalidating all previous pointers,
|
||||||
// and in particular, *all* raw pointers.
|
// and in particular, *all* raw pointers.
|
||||||
MemoryKind::Stack => (extra.base_ptr_tag(id), Permission::Unique),
|
MemoryKind::Stack => (extra.base_ptr_tag(id, ¤t_span), Permission::Unique),
|
||||||
// Everything else is shared by default.
|
// Everything else is shared by default.
|
||||||
_ => (extra.base_ptr_tag(id), Permission::SharedReadWrite),
|
_ => (extra.base_ptr_tag(id, ¤t_span), Permission::SharedReadWrite),
|
||||||
};
|
};
|
||||||
Stacks::new(size, perm, base_tag, id, &mut current_span)
|
Stacks::new(size, perm, base_tag, id, &mut current_span)
|
||||||
}
|
}
|
||||||
|
@ -674,7 +674,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriEvalContextEx
|
||||||
-> InterpResult<'tcx> {
|
-> InterpResult<'tcx> {
|
||||||
let global = this.machine.stacked_borrows.as_ref().unwrap().borrow();
|
let global = this.machine.stacked_borrows.as_ref().unwrap().borrow();
|
||||||
if global.tracked_pointer_tags.contains(&new_tag) {
|
if global.tracked_pointer_tags.contains(&new_tag) {
|
||||||
register_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
|
this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
|
||||||
new_tag.0,
|
new_tag.0,
|
||||||
loc.map(|(alloc_id, base_offset, _)| (alloc_id, alloc_range(base_offset, size))),
|
loc.map(|(alloc_id, base_offset, _)| (alloc_id, alloc_range(base_offset, size))),
|
||||||
));
|
));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue