Skip to content

Commit 50e7b90

Browse files
committed
Print spans where tags are created and invalidated
1 parent 8e818ff commit 50e7b90

File tree

6 files changed

+328
-45
lines changed

6 files changed

+328
-45
lines changed

cargo-miri/bin.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,51 @@ path = "lib.rs"
482482
}
483483
}
484484

485+
fn collect_local_crate_names() {
486+
#[derive(Deserialize)]
487+
struct Metadata {
488+
workspace_members: Vec<String>,
489+
}
490+
let mut cmd = cargo();
491+
// `-Zunstable-options` is required by `--config`.
492+
cmd.args(["metadata", "--no-deps", "--format-version=1", "-Zunstable-options"]);
493+
// The `build.target-dir` config can be passed by `--config` flags, so forward them to
494+
// `cargo metadata`.
495+
let config_flag = "--config";
496+
for arg in ArgSplitFlagValue::new(
497+
env::args().skip(3), // skip the program name, "miri" and "run" / "test"
498+
config_flag,
499+
) {
500+
if let Ok(config) = arg {
501+
cmd.arg(config_flag).arg(config);
502+
}
503+
}
504+
let mut child = cmd
505+
.stdin(process::Stdio::null())
506+
.stdout(process::Stdio::piped())
507+
.spawn()
508+
.expect("failed ro run `cargo metadata`");
509+
// Check this `Result` after `status.success()` is checked, so we don't print the error
510+
// to stderr if `cargo metadata` is also printing to stderr.
511+
let metadata: Result<Metadata, _> = serde_json::from_reader(child.stdout.take().unwrap());
512+
let status = child.wait().expect("failed to wait for `cargo metadata` to exit");
513+
if !status.success() {
514+
std::process::exit(status.code().unwrap_or(-1));
515+
}
516+
let metadata = metadata
517+
.unwrap_or_else(|e| show_error(format!("invalid `cargo metadata` output: {}", e)));
518+
assert!(metadata.workspace_members.len() > 0);
519+
520+
let mut flags = env::var("MIRIFLAGS").unwrap_or_default();
521+
if !flags.contains("-Zmiri-local-crates") {
522+
for member in metadata.workspace_members {
523+
flags += &format!(" -Zmiri-local-crates={}", member.split(" ").nth(0).unwrap().replace("-", "_"));
524+
}
525+
}
526+
env::set_var("MIRIFLAGS", flags);
527+
}
528+
529+
485530
/// Detect the target directory by calling `cargo metadata`.
486531
fn detect_target_dir() -> PathBuf {
487532
#[derive(Deserialize)]
@@ -595,6 +640,8 @@ fn phase_cargo_miri(mut args: env::Args) {
595640
}
596641
}
597642

643+
collect_local_crate_names();
644+
598645
// Detect the target directory if it's not specified via `--target-dir`.
599646
let target_dir = target_dir.get_or_insert_with(detect_target_dir);
600647

src/bin/miri.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,11 @@ fn main() {
465465
_ => panic!("-Zmiri-backtrace may only be 0, 1, or full"),
466466
};
467467
}
468+
arg if arg.starts_with("-Zmiri-local-crates=") => {
469+
miri_config
470+
.local_crates
471+
.push(arg.strip_prefix("-Zmiri-local-crates=").unwrap().to_string());
472+
}
468473
_ => {
469474
// Forward to rustc.
470475
rustc_args.push(arg);

src/diagnostics.rs

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@ use std::num::NonZeroU64;
44

55
use log::trace;
66

7-
use rustc_middle::ty::{self, TyCtxt};
7+
use rustc_middle::ty;
88
use rustc_span::{source_map::DUMMY_SP, Span, SpanData, Symbol};
99

10-
use crate::stacked_borrows::{AccessKind, SbTag};
10+
use crate::stacked_borrows::{AccessKind, SbTag, TagHistory};
1111
use crate::*;
1212

13+
struct HexRange(AllocRange);
14+
15+
impl std::fmt::Display for HexRange {
16+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17+
write!(f, "[{:#x}..{:#x}]", self.0.start.bytes(), self.0.end().bytes())
18+
}
19+
}
20+
1321
/// Details of premature program termination.
1422
pub enum TerminationInfo {
1523
Exit(i64),
@@ -19,6 +27,7 @@ pub enum TerminationInfo {
1927
msg: String,
2028
help: Option<String>,
2129
url: String,
30+
history: Option<TagHistory>,
2231
},
2332
Deadlock,
2433
MultipleSymbolDefinitions {
@@ -94,7 +103,8 @@ fn prune_stacktrace<'mir, 'tcx>(
94103
// Only prune frames if there is at least one local frame. This check ensures that if
95104
// we get a backtrace that never makes it to the user code because it has detected a
96105
// bug in the Rust runtime, we don't prune away every frame.
97-
let has_local_frame = stacktrace.iter().any(|frame| frame.instance.def_id().is_local());
106+
let has_local_frame =
107+
stacktrace.iter().any(|frame| ecx.machine.is_local(frame.instance.def_id()));
98108
if has_local_frame {
99109
// This is part of the logic that `std` uses to select the relevant part of a
100110
// backtrace. But here, we only look for __rust_begin_short_backtrace, not
@@ -115,7 +125,9 @@ fn prune_stacktrace<'mir, 'tcx>(
115125
// This len check ensures that we don't somehow remove every frame, as doing so breaks
116126
// the primary error message.
117127
while stacktrace.len() > 1
118-
&& stacktrace.last().map_or(false, |e| !e.instance.def_id().is_local())
128+
&& stacktrace
129+
.last()
130+
.map_or(false, |frame| !ecx.machine.is_local(frame.instance.def_id()))
119131
{
120132
stacktrace.pop();
121133
}
@@ -155,12 +167,38 @@ pub fn report_error<'tcx, 'mir>(
155167
(None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation;")),
156168
(None, format!("or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning")),
157169
],
158-
ExperimentalUb { url, help, .. } => {
170+
ExperimentalUb { url, help, history, .. } => {
159171
msg.extend(help.clone());
160-
vec![
172+
let mut helps = vec![
161173
(None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental")),
162-
(None, format!("see {} for further information", url))
163-
]
174+
(None, format!("see {} for further information", url)),
175+
];
176+
match history {
177+
Some(TagHistory::Tagged {tag, created: (created_range, created_span), invalidated}) => {
178+
let msg = format!("{:?} was created due to a retag at offsets {}", tag, HexRange(*created_range));
179+
helps.push((Some(created_span.clone()), msg));
180+
if let Some((invalidated_range, invalidated_span)) = invalidated {
181+
let msg = format!("{:?} was later invalidated due to a retag at offsets {}", tag, HexRange(*invalidated_range));
182+
helps.push((Some(invalidated_span.clone()), msg));
183+
}
184+
}
185+
Some(TagHistory::Untagged{ recently_created, recently_invalidated, matching_created }) => {
186+
if let Some((range, span)) = recently_created {
187+
let msg = format!("tag was most recently created at offsets {}", HexRange(*range));
188+
helps.push((Some(span.clone()), msg));
189+
}
190+
if let Some((range, span)) = recently_invalidated {
191+
let msg = format!("tag was later invalidated at offsets {}", HexRange(*range));
192+
helps.push((Some(span.clone()), msg));
193+
}
194+
if let Some((range, span)) = matching_created {
195+
let msg = format!("this tag was also created here at offsets {}", HexRange(*range));
196+
helps.push((Some(span.clone()), msg));
197+
}
198+
}
199+
None => {}
200+
}
201+
helps
164202
}
165203
MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
166204
vec![
@@ -218,7 +256,7 @@ pub fn report_error<'tcx, 'mir>(
218256
e.print_backtrace();
219257
msg.insert(0, e.to_string());
220258
report_msg(
221-
*ecx.tcx,
259+
ecx,
222260
DiagLevel::Error,
223261
&if let Some(title) = title { format!("{}: {}", title, msg[0]) } else { msg[0].clone() },
224262
msg,
@@ -264,19 +302,20 @@ pub fn report_error<'tcx, 'mir>(
264302
/// We want to present a multi-line span message for some errors. Diagnostics do not support this
265303
/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
266304
/// additional `span_label` or `note` call.
267-
fn report_msg<'tcx>(
268-
tcx: TyCtxt<'tcx>,
305+
fn report_msg<'mir, 'tcx>(
306+
ecx: &MiriEvalContext<'mir, 'tcx>,
269307
diag_level: DiagLevel,
270308
title: &str,
271309
span_msg: Vec<String>,
272310
mut helps: Vec<(Option<SpanData>, String)>,
273311
stacktrace: &[FrameInfo<'tcx>],
274312
) {
275313
let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
314+
let sess = ecx.tcx.sess;
276315
let mut err = match diag_level {
277-
DiagLevel::Error => tcx.sess.struct_span_err(span, title).forget_guarantee(),
278-
DiagLevel::Warning => tcx.sess.struct_span_warn(span, title),
279-
DiagLevel::Note => tcx.sess.diagnostic().span_note_diag(span, title),
316+
DiagLevel::Error => sess.struct_span_err(span, title).forget_guarantee(),
317+
DiagLevel::Warning => sess.struct_span_warn(span, title),
318+
DiagLevel::Note => sess.diagnostic().span_note_diag(span, title),
280319
};
281320

282321
// Show main message.
@@ -306,7 +345,7 @@ fn report_msg<'tcx>(
306345
}
307346
// Add backtrace
308347
for (idx, frame_info) in stacktrace.iter().enumerate() {
309-
let is_local = frame_info.instance.def_id().is_local();
348+
let is_local = ecx.machine.is_local(frame_info.instance.def_id());
310349
// No span for non-local frames and the first frame (which is the error site).
311350
if is_local && idx > 0 {
312351
err.span_note(frame_info.span, &frame_info.to_string());
@@ -426,7 +465,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
426465
_ => ("tracking was triggered", DiagLevel::Note),
427466
};
428467

429-
report_msg(*this.tcx, diag_level, title, vec![msg], vec![], &stacktrace);
468+
report_msg(this, diag_level, title, vec![msg], vec![], &stacktrace);
430469
}
431470
});
432471
}

src/eval.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ pub struct MiriConfig {
111111
/// Panic when unsupported functionality is encountered
112112
pub panic_on_unsupported: bool,
113113
pub backtrace_style: BacktraceStyle,
114+
/// Crates which are considered local for the purposes of error reporting
115+
pub local_crates: Vec<String>,
114116
}
115117

116118
impl Default for MiriConfig {
@@ -136,6 +138,7 @@ impl Default for MiriConfig {
136138
measureme_out: None,
137139
panic_on_unsupported: false,
138140
backtrace_style: BacktraceStyle::Short,
141+
local_crates: Vec::new(),
139142
}
140143
}
141144
}
@@ -279,6 +282,20 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
279282
Ok((ecx, ret_place))
280283
}
281284

285+
// This is potentially a performance hazard.
286+
// Factoring it into its own function lets us keep an eye on how much it shows up in a profile.
287+
fn set_current_span<'mir, 'tcx: 'mir>(ecx: &mut MiriEvalContext<'mir, 'tcx>) {
288+
let current_span = Machine::stack(&ecx)
289+
.into_iter()
290+
.rev()
291+
.find(|frame| ecx.machine.is_local(frame.instance.def_id()))
292+
.map(|frame| frame.current_span())
293+
.unwrap_or(rustc_span::DUMMY_SP);
294+
if let Some(sb) = ecx.memory.extra.stacked_borrows.as_mut() {
295+
sb.get_mut().current_span = current_span;
296+
}
297+
}
298+
282299
/// Evaluates the entry function specified by `entry_id`.
283300
/// Returns `Some(return_code)` if program executed completed.
284301
/// Returns `None` if an evaluation error occured.
@@ -306,6 +323,9 @@ pub fn eval_entry<'tcx>(
306323
let info = ecx.preprocess_diagnostics();
307324
match ecx.schedule()? {
308325
SchedulingAction::ExecuteStep => {
326+
if ecx.memory.extra.stacked_borrows.is_some() {
327+
set_current_span(&mut ecx);
328+
}
309329
assert!(ecx.step()?, "a terminated thread was scheduled for execution");
310330
}
311331
SchedulingAction::ExecuteTimeoutCallback => {

src/machine.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustc_middle::{
1919
Instance, TyCtxt,
2020
},
2121
};
22-
use rustc_span::def_id::DefId;
22+
use rustc_span::def_id::{CrateNum, DefId};
2323
use rustc_span::symbol::{sym, Symbol};
2424
use rustc_target::abi::Size;
2525
use rustc_target::spec::abi::Abi;
@@ -349,10 +349,23 @@ pub struct Evaluator<'mir, 'tcx> {
349349

350350
/// Equivalent setting as RUST_BACKTRACE on encountering an error.
351351
pub(crate) backtrace_style: BacktraceStyle,
352+
353+
/// Crates which are considered local for the purposes of error reporting
354+
pub(crate) local_crates: Vec<CrateNum>,
352355
}
353356

354357
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
355358
pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Self {
359+
// Convert the local crate names from the passed-in config into CrateNums so that they can
360+
// be looked up quickly during execution
361+
let mut local_crates = Vec::new();
362+
for num in layout_cx.tcx.crates(()) {
363+
let name = layout_cx.tcx.crate_name(*num).as_str().to_string();
364+
if config.local_crates.contains(&name) {
365+
local_crates.push(*num);
366+
}
367+
}
368+
356369
let layouts =
357370
PrimitiveLayouts::new(layout_cx).expect("Couldn't get layouts of primitive types");
358371
let profiler = config.measureme_out.as_ref().map(|out| {
@@ -381,12 +394,17 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
381394
exported_symbols_cache: FxHashMap::default(),
382395
panic_on_unsupported: config.panic_on_unsupported,
383396
backtrace_style: config.backtrace_style,
397+
local_crates,
384398
}
385399
}
386400

387401
pub(crate) fn communicate(&self) -> bool {
388402
self.isolated_op == IsolatedOp::Allow
389403
}
404+
405+
pub(crate) fn is_local(&self, def_id: DefId) -> bool {
406+
def_id.is_local() || self.local_crates.contains(&def_id.krate)
407+
}
390408
}
391409

392410
/// A rustc InterpCx for Miri.

0 commit comments

Comments
 (0)