Skip to content

Commit 5d72cd9

Browse files
committed
Auto merge of #2024 - saethlin:better-local-check, r=RalfJung
Consider the cargo workspace when checking if a frame is local `DefId::is_local` returns a result which is technically correct, but doesn't match the user's intuition when running integration tests or doctests. This incorporates the workspace crates mentioned in `cargo metadata` into the check for whether a frame is local to match user intuition. For example, here is the backtrace you get from `MIRIFLAGS=-Zmiri-tag-raw-pointers cargo miri test` in `bytes` 1.1.0: ``` --> /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/slice/raw.rs:131:14 | 131 | unsafe { &mut *ptr::slice_from_raw_parts_mut(data, len) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to reborrow for Unique at alloc67158, but parent tag <untagged> does not have an appropriate item in the borrow stack | = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information = note: inside `std::slice::from_raw_parts_mut::<u8>` at /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/slice/raw.rs:131:14 = note: inside `bytes::bytes::rebuild_boxed_slice` at /tmp/bytes-1.1.0/src/bytes.rs:938:19 = note: inside closure at /tmp/bytes-1.1.0/src/bytes.rs:904:18 = note: inside `<std::sync::atomic::AtomicPtr<()> as bytes::loom::sync::atomic::AtomicMut<()>>::with_mut::<[closure@bytes::bytes::promotable_even_drop::{closure#0}], ()>` at /tmp/bytes-1.1.0/src/loom.rs:17:17 = note: inside `bytes::bytes::promotable_even_drop` at /tmp/bytes-1.1.0/src/bytes.rs:895:5 = note: inside `<bytes::Bytes as std::ops::Drop>::drop` at /tmp/bytes-1.1.0/src/bytes.rs:515:18 = note: inside `std::ptr::drop_in_place::<bytes::Bytes> - shim(Some(bytes::Bytes))` at /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:188:1 note: inside `copy_to_bytes_less` at tests/test_buf.rs:112:1 --> tests/test_buf.rs:112:1 | 112 | } | ^ note: inside closure at tests/test_buf.rs:106:1 --> tests/test_buf.rs:106:1 | 105 | #[test] | ------- in this procedural macro expansion 106 | / fn copy_to_bytes_less() { 107 | | let mut buf = &b"hello world"[..]; 108 | | 109 | | let bytes = buf.copy_to_bytes(5); 110 | | assert_eq!(bytes, &b"hello"[..]); 111 | | assert_eq!(buf, &b" world"[..]) 112 | | } | |_^ = note: this error originates in the attribute macro `test` (in Nightly builds, run with -Z macro-backtrace for more info) ``` We get these because the integration tests are occurring in a crate called `test`, not the actual `bytes` crate. With this PR, we get this: ``` = note: inside `std::slice::from_raw_parts_mut::<u8>` at /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/slice/raw.rs:131:14 note: inside `bytes::bytes::rebuild_boxed_slice` at /tmp/bytes-1.1.0/src/bytes.rs:938:19 --> /tmp/bytes-1.1.0/src/bytes.rs:938:19 | 938 | Box::from_raw(slice::from_raw_parts_mut(buf, cap)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: inside closure at /tmp/bytes-1.1.0/src/bytes.rs:904:18 --> /tmp/bytes-1.1.0/src/bytes.rs:904:18 | 904 | drop(rebuild_boxed_slice(buf, ptr, len)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: inside `<std::sync::atomic::AtomicPtr<()> as bytes::loom::sync::atomic::AtomicMut<()>>::with_mut::<[closure@bytes::bytes::promotable_even_drop::{closure#0}], ()>` at /tmp/bytes-1.1.0/src/loom.rs:17:17 --> /tmp/bytes-1.1.0/src/loom.rs:17:17 | 17 | f(self.get_mut()) | ^^^^^^^^^^^^^^^^^ note: inside `bytes::bytes::promotable_even_drop` at /tmp/bytes-1.1.0/src/bytes.rs:895:5 --> /tmp/bytes-1.1.0/src/bytes.rs:895:5 | 895 | / data.with_mut(|shared| { 896 | | let shared = *shared; 897 | | let kind = shared as usize & KIND_MASK; 898 | | ... | 905 | | } 906 | | }); | |______^ note: inside `<bytes::Bytes as std::ops::Drop>::drop` at /tmp/bytes-1.1.0/src/bytes.rs:515:18 --> /tmp/bytes-1.1.0/src/bytes.rs:515:18 | 515 | unsafe { (self.vtable.drop)(&mut self.data, self.ptr, self.len) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: inside `std::ptr::drop_in_place::<bytes::Bytes> - shim(Some(bytes::Bytes))` at /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:188:1 note: inside `copy_to_bytes_less` at tests/test_buf.rs:112:1 --> tests/test_buf.rs:112:1 | 112 | } | ^ note: inside closure at tests/test_buf.rs:106:1 --> tests/test_buf.rs:106:1 | 105 | #[test] | ------- in this procedural macro expansion 106 | / fn copy_to_bytes_less() { 107 | | let mut buf = &b"hello world"[..]; 108 | | 109 | | let bytes = buf.copy_to_bytes(5); 110 | | assert_eq!(bytes, &b"hello"[..]); 111 | | assert_eq!(buf, &b" world"[..]) 112 | | } | |_^ = note: this error originates in the attribute macro `test` (in Nightly builds, run with -Z macro-backtrace for more info) ``` Note that this kind of inflation is rather rare to see. Most backtraces change not at all or only a tiny bit. I originally implemented this to support another improvement to Miri diagnostics, but I think this is hairy enough to deserve its own PR, if somewhat poorly-motivated.
2 parents 8e818ff + 65125df commit 5d72cd9

File tree

5 files changed

+79
-24
lines changed

5 files changed

+79
-24
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,12 @@ binaries, and as such worth documenting:
375375
directory after loading all the source files, but before commencing
376376
interpretation. This is useful if the interpreted program wants a different
377377
working directory at run-time than at build-time.
378+
* `MIRI_LOCAL_CRATES` is set by `cargo-miri` to tell the Miri driver which
379+
crates should be given special treatment in diagnostics, in addition to the
380+
crate currently being compiled.
378381
* `MIRI_VERBOSE` when set to any value tells the various `cargo-miri` phases to
379382
perform verbose logging.
380-
383+
381384
[testing-miri]: CONTRIBUTING.md#testing-the-miri-driver
382385

383386
## Miri `extern` functions

cargo-miri/bin.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -482,12 +482,13 @@ path = "lib.rs"
482482
}
483483
}
484484

485-
/// Detect the target directory by calling `cargo metadata`.
486-
fn detect_target_dir() -> PathBuf {
487-
#[derive(Deserialize)]
488-
struct Metadata {
489-
target_directory: PathBuf,
490-
}
485+
#[derive(Deserialize)]
486+
struct Metadata {
487+
target_directory: PathBuf,
488+
workspace_members: Vec<String>,
489+
}
490+
491+
fn get_cargo_metadata() -> Metadata {
491492
let mut cmd = cargo();
492493
// `-Zunstable-options` is required by `--config`.
493494
cmd.args(["metadata", "--no-deps", "--format-version=1", "-Zunstable-options"]);
@@ -514,9 +515,25 @@ fn detect_target_dir() -> PathBuf {
514515
if !status.success() {
515516
std::process::exit(status.code().unwrap_or(-1));
516517
}
517-
metadata
518-
.unwrap_or_else(|e| show_error(format!("invalid `cargo metadata` output: {}", e)))
519-
.target_directory
518+
metadata.unwrap_or_else(|e| show_error(format!("invalid `cargo metadata` output: {}", e)))
519+
}
520+
521+
/// Pulls all the crates in this workspace from the cargo metadata.
522+
/// Workspace members are emitted like "miri 0.1.0 (path+file:///path/to/miri)"
523+
/// Additionally, somewhere between cargo metadata and TyCtxt, '-' gets replaced with '_' so we
524+
/// make that same transformation here.
525+
fn local_crates(metadata: &Metadata) -> String {
526+
assert!(metadata.workspace_members.len() > 0);
527+
let mut local_crates = String::new();
528+
for member in &metadata.workspace_members {
529+
let name = member.split(" ").nth(0).unwrap();
530+
let name = name.replace("-", "_");
531+
local_crates.push_str(&name);
532+
local_crates.push(',');
533+
}
534+
local_crates.pop(); // Remove the trailing ','
535+
536+
local_crates
520537
}
521538

522539
fn phase_cargo_miri(mut args: env::Args) {
@@ -595,8 +612,10 @@ fn phase_cargo_miri(mut args: env::Args) {
595612
}
596613
}
597614

615+
let metadata = get_cargo_metadata();
616+
598617
// Detect the target directory if it's not specified via `--target-dir`.
599-
let target_dir = target_dir.get_or_insert_with(detect_target_dir);
618+
let target_dir = target_dir.get_or_insert_with(|| metadata.target_directory.clone());
600619

601620
// Set `--target-dir` to `miri` inside the original target directory.
602621
target_dir.push("miri");
@@ -628,6 +647,8 @@ fn phase_cargo_miri(mut args: env::Args) {
628647
// Set rustdoc to us as well, so we can run doctests.
629648
cmd.env("RUSTDOC", &cargo_miri_path);
630649

650+
cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
651+
631652
// Run cargo.
632653
if verbose {
633654
eprintln!("[cargo-miri miri] RUSTC_WRAPPER={:?}", cargo_miri_path);

src/diagnostics.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ 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

1010
use crate::stacked_borrows::{AccessKind, SbTag};
@@ -94,7 +94,7 @@ fn prune_stacktrace<'mir, 'tcx>(
9494
// Only prune frames if there is at least one local frame. This check ensures that if
9595
// we get a backtrace that never makes it to the user code because it has detected a
9696
// 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());
97+
let has_local_frame = stacktrace.iter().any(|frame| ecx.machine.is_local(frame));
9898
if has_local_frame {
9999
// This is part of the logic that `std` uses to select the relevant part of a
100100
// backtrace. But here, we only look for __rust_begin_short_backtrace, not
@@ -115,7 +115,7 @@ fn prune_stacktrace<'mir, 'tcx>(
115115
// This len check ensures that we don't somehow remove every frame, as doing so breaks
116116
// the primary error message.
117117
while stacktrace.len() > 1
118-
&& stacktrace.last().map_or(false, |e| !e.instance.def_id().is_local())
118+
&& stacktrace.last().map_or(false, |frame| !ecx.machine.is_local(frame))
119119
{
120120
stacktrace.pop();
121121
}
@@ -218,7 +218,7 @@ pub fn report_error<'tcx, 'mir>(
218218
e.print_backtrace();
219219
msg.insert(0, e.to_string());
220220
report_msg(
221-
*ecx.tcx,
221+
ecx,
222222
DiagLevel::Error,
223223
&if let Some(title) = title { format!("{}: {}", title, msg[0]) } else { msg[0].clone() },
224224
msg,
@@ -264,19 +264,20 @@ pub fn report_error<'tcx, 'mir>(
264264
/// We want to present a multi-line span message for some errors. Diagnostics do not support this
265265
/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
266266
/// additional `span_label` or `note` call.
267-
fn report_msg<'tcx>(
268-
tcx: TyCtxt<'tcx>,
267+
fn report_msg<'mir, 'tcx>(
268+
ecx: &InterpCx<'mir, 'tcx, Evaluator<'mir, 'tcx>>,
269269
diag_level: DiagLevel,
270270
title: &str,
271271
span_msg: Vec<String>,
272272
mut helps: Vec<(Option<SpanData>, String)>,
273273
stacktrace: &[FrameInfo<'tcx>],
274274
) {
275275
let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
276+
let sess = ecx.tcx.sess;
276277
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),
278+
DiagLevel::Error => sess.struct_span_err(span, title).forget_guarantee(),
279+
DiagLevel::Warning => sess.struct_span_warn(span, title),
280+
DiagLevel::Note => sess.diagnostic().span_note_diag(span, title),
280281
};
281282

282283
// Show main message.
@@ -306,7 +307,7 @@ fn report_msg<'tcx>(
306307
}
307308
// Add backtrace
308309
for (idx, frame_info) in stacktrace.iter().enumerate() {
309-
let is_local = frame_info.instance.def_id().is_local();
310+
let is_local = ecx.machine.is_local(frame_info);
310311
// No span for non-local frames and the first frame (which is the error site).
311312
if is_local && idx > 0 {
312313
err.span_note(frame_info.span, &frame_info.to_string());
@@ -426,7 +427,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
426427
_ => ("tracking was triggered", DiagLevel::Note),
427428
};
428429

429-
report_msg(*this.tcx, diag_level, title, vec![msg], vec![], &stacktrace);
430+
report_msg(this, diag_level, title, vec![msg], vec![], &stacktrace);
430431
}
431432
});
432433
}

src/helpers.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_middle::ty::{
1212
layout::{LayoutOf, TyAndLayout},
1313
List, TyCtxt,
1414
};
15-
use rustc_span::Symbol;
15+
use rustc_span::{def_id::CrateNum, Symbol};
1616
use rustc_target::abi::{Align, FieldsShape, Size, Variants};
1717
use rustc_target::spec::abi::Abi;
1818

@@ -775,3 +775,22 @@ pub fn isolation_abort_error(name: &str) -> InterpResult<'static> {
775775
name,
776776
)))
777777
}
778+
779+
/// Retrieve the list of local crates that should have been passed by cargo-miri in
780+
/// MIRI_LOCAL_CRATES and turn them into `CrateNum`s.
781+
pub fn get_local_crates(tcx: &TyCtxt<'_>) -> Vec<CrateNum> {
782+
// Convert the local crate names from the passed-in config into CrateNums so that they can
783+
// be looked up quickly during execution
784+
let local_crate_names = std::env::var("MIRI_LOCAL_CRATES")
785+
.map(|crates| crates.split(",").map(|krate| krate.to_string()).collect::<Vec<_>>())
786+
.unwrap_or_default();
787+
let mut local_crates = Vec::new();
788+
for &crate_num in tcx.crates(()) {
789+
let name = tcx.crate_name(crate_num);
790+
let name = name.as_str();
791+
if local_crate_names.iter().any(|local_name| local_name == name) {
792+
local_crates.push(crate_num);
793+
}
794+
}
795+
local_crates
796+
}

src/machine.rs

Lines changed: 12 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,14 @@ 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+
let local_crates = helpers::get_local_crates(&layout_cx.tcx);
356360
let layouts =
357361
PrimitiveLayouts::new(layout_cx).expect("Couldn't get layouts of primitive types");
358362
let profiler = config.measureme_out.as_ref().map(|out| {
@@ -381,12 +385,19 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
381385
exported_symbols_cache: FxHashMap::default(),
382386
panic_on_unsupported: config.panic_on_unsupported,
383387
backtrace_style: config.backtrace_style,
388+
local_crates,
384389
}
385390
}
386391

387392
pub(crate) fn communicate(&self) -> bool {
388393
self.isolated_op == IsolatedOp::Allow
389394
}
395+
396+
/// Check whether the stack frame that this `FrameInfo` refers to is part of a local crate.
397+
pub(crate) fn is_local(&self, frame: &FrameInfo<'_>) -> bool {
398+
let def_id = frame.instance.def_id();
399+
def_id.is_local() || self.local_crates.contains(&def_id.krate)
400+
}
390401
}
391402

392403
/// A rustc InterpCx for Miri.

0 commit comments

Comments
 (0)