From 606ca4da7ea3ba7ea4caec41709bd4952daf3c3b Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sun, 12 Mar 2023 18:30:33 -0400 Subject: [PATCH 1/3] Report a backtrace for memory leaks under Miri --- .../src/interpret/eval_context.rs | 7 +--- .../rustc_const_eval/src/interpret/machine.rs | 2 +- .../rustc_const_eval/src/interpret/memory.rs | 27 +++++++------ src/tools/miri/src/borrow_tracker/mod.rs | 2 +- src/tools/miri/src/diagnostics.rs | 39 +++++++++++++++++-- src/tools/miri/src/eval.rs | 11 ++++-- src/tools/miri/src/machine.rs | 39 +++++++++++++------ src/tools/miri/src/tag_gc.rs | 2 +- src/tools/miri/tests/fail/memleak.rs | 2 +- src/tools/miri/tests/fail/memleak.stderr | 23 ++++++++--- .../miri/tests/fail/memleak_rc.32bit.stderr | 24 +++++++++--- .../miri/tests/fail/memleak_rc.64bit.stderr | 25 ++++++++---- src/tools/miri/tests/fail/memleak_rc.rs | 2 +- 13 files changed, 145 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 3e58a58aef7d0..b5b5cc4f19638 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -132,11 +132,10 @@ pub struct Frame<'mir, 'tcx, Prov: Provenance = AllocId, Extra = ()> { } /// What we store about a frame in an interpreter backtrace. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct FrameInfo<'tcx> { pub instance: ty::Instance<'tcx>, pub span: Span, - pub lint_root: Option, } #[derive(Clone, Copy, Eq, PartialEq, Debug)] // Miri debug-prints these @@ -947,10 +946,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // This deliberately does *not* honor `requires_caller_location` since it is used for much // more than just panics. for frame in stack.iter().rev() { - let lint_root = frame.lint_root(); let span = frame.current_span(); - - frames.push(FrameInfo { span, instance: frame.instance, lint_root }); + frames.push(FrameInfo { span, instance: frame.instance }); } trace!("generate stacktrace: {:#?}", frames); frames diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 0291cca7378a4..b448e3a24c68f 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -104,7 +104,7 @@ pub trait Machine<'mir, 'tcx>: Sized { type FrameExtra; /// Extra data stored in every allocation. - type AllocExtra: Debug + Clone + 'static; + type AllocExtra: Debug + Clone + 'tcx; /// Type for the bytes of the allocation. type Bytes: AllocBytes + 'static; diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index a3764a7d14266..d5b6a581a79f6 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -215,7 +215,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { self.allocate_raw_ptr(alloc, kind) } - /// This can fail only of `alloc` contains provenance. + /// This can fail only if `alloc` contains provenance. pub fn allocate_raw_ptr( &mut self, alloc: Allocation, @@ -807,9 +807,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { DumpAllocs { ecx: self, allocs } } - /// Print leaked memory. Allocations reachable from `static_roots` or a `Global` allocation - /// are not considered leaked. Leaks whose kind `may_leak()` returns true are not reported. - pub fn leak_report(&self, static_roots: &[AllocId]) -> usize { + /// Find leaked allocations. Allocations reachable from `static_roots` or a `Global` allocation + /// are not considered leaked, as well as leaks whose kind's `may_leak()` returns true. + pub fn find_leaked_allocations( + &self, + static_roots: &[AllocId], + ) -> Vec<(AllocId, MemoryKind, Allocation)> + { // Collect the set of allocations that are *reachable* from `Global` allocations. let reachable = { let mut reachable = FxHashSet::default(); @@ -833,14 +837,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { }; // All allocations that are *not* `reachable` and *not* `may_leak` are considered leaking. - let leaks: Vec<_> = self.memory.alloc_map.filter_map_collect(|&id, &(kind, _)| { - if kind.may_leak() || reachable.contains(&id) { None } else { Some(id) } - }); - let n = leaks.len(); - if n > 0 { - eprintln!("The following memory was leaked: {:?}", self.dump_allocs(leaks)); - } - n + self.memory.alloc_map.filter_map_collect(|id, (kind, alloc)| { + if kind.may_leak() || reachable.contains(id) { + None + } else { + Some((*id, *kind, alloc.clone())) + } + }) } } diff --git a/src/tools/miri/src/borrow_tracker/mod.rs b/src/tools/miri/src/borrow_tracker/mod.rs index ed958329f9514..d54adb72887a0 100644 --- a/src/tools/miri/src/borrow_tracker/mod.rs +++ b/src/tools/miri/src/borrow_tracker/mod.rs @@ -352,7 +352,7 @@ pub enum AllocState { TreeBorrows(Box>), } -impl machine::AllocExtra { +impl machine::AllocExtra<'_> { #[track_caller] pub fn borrow_tracker_sb(&self) -> &RefCell { match self.borrow_tracker { diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 3c13118122ca3..7a726be00da4e 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -105,7 +105,7 @@ pub enum NonHaltingDiagnostic { } /// Level of Miri specific diagnostics -enum DiagLevel { +pub enum DiagLevel { Error, Warning, Note, @@ -114,7 +114,7 @@ enum DiagLevel { /// 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 /// be pointing to a problem in the Rust runtime itself, and do not prune it at all. -fn prune_stacktrace<'tcx>( +pub fn prune_stacktrace<'tcx>( mut stacktrace: Vec>, machine: &MiriMachine<'_, 'tcx>, ) -> (Vec>, bool) { @@ -338,12 +338,45 @@ pub fn report_error<'tcx, 'mir>( None } +pub fn report_leaks<'mir, 'tcx>( + ecx: &InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>, + leaks: Vec<(AllocId, MemoryKind, Allocation>)>, +) { + let mut any_pruned = false; + for (id, kind, mut alloc) in leaks { + let Some(backtrace) = alloc.extra.backtrace.take() else { + continue; + }; + let (backtrace, pruned) = prune_stacktrace(backtrace, &ecx.machine); + any_pruned |= pruned; + report_msg( + DiagLevel::Error, + &format!( + "memory leaked: {id:?} ({}, size: {:?}, align: {:?}), allocated here:", + kind, + alloc.size().bytes(), + alloc.align.bytes() + ), + vec![], + vec![], + vec![], + &backtrace, + &ecx.machine, + ); + } + if any_pruned { + ecx.tcx.sess.diagnostic().note_without_error( + "some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace", + ); + } +} + /// Report an error or note (depending on the `error` argument) with the given stacktrace. /// Also emits a full stacktrace of the interpreter stack. /// 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` and display each line after the first with an /// additional `span_label` or `note` call. -fn report_msg<'tcx>( +pub fn report_msg<'tcx>( diag_level: DiagLevel, title: &str, span_msg: Vec, diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index a32b18595b536..60533687bed57 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -10,6 +10,7 @@ use std::thread; use log::info; use crate::borrow_tracker::RetagFields; +use crate::diagnostics::report_leaks; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::Namespace; use rustc_hir::def_id::DefId; @@ -457,10 +458,12 @@ pub fn eval_entry<'tcx>( } // Check for memory leaks. info!("Additonal static roots: {:?}", ecx.machine.static_roots); - let leaks = ecx.leak_report(&ecx.machine.static_roots); - if leaks != 0 { - tcx.sess.err("the evaluated program leaked memory"); - tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check"); + let leaks = ecx.find_leaked_allocations(&ecx.machine.static_roots); + if !leaks.is_empty() { + report_leaks(&ecx, leaks); + tcx.sess.note_without_error( + "the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check", + ); // Ignore the provided return code - let the reported error // determine the return code. return None; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 477d8d33ebba7..a9c1357fcf298 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -253,20 +253,24 @@ impl ProvenanceExtra { /// Extra per-allocation data #[derive(Debug, Clone)] -pub struct AllocExtra { +pub struct AllocExtra<'tcx> { /// Global state of the borrow tracker, if enabled. pub borrow_tracker: Option, - /// Data race detection via the use of a vector-clock, - /// this is only added if it is enabled. + /// Data race detection via the use of a vector-clock. + /// This is only added if it is enabled. pub data_race: Option, - /// Weak memory emulation via the use of store buffers, - /// this is only added if it is enabled. + /// Weak memory emulation via the use of store buffers. + /// This is only added if it is enabled. pub weak_memory: Option, + /// A backtrace to where this allocation was allocated. + /// As this is recorded for leak reports, it only exists + /// if this allocation is leakable. + pub backtrace: Option>>, } -impl VisitTags for AllocExtra { +impl VisitTags for AllocExtra<'_> { fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) { - let AllocExtra { borrow_tracker, data_race, weak_memory } = self; + let AllocExtra { borrow_tracker, data_race, weak_memory, backtrace: _ } = self; borrow_tracker.visit_tags(visit); data_race.visit_tags(visit); @@ -773,7 +777,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { type ExtraFnVal = Dlsym; type FrameExtra = FrameExtra<'tcx>; - type AllocExtra = AllocExtra; + type AllocExtra = AllocExtra<'tcx>; type Provenance = Provenance; type ProvenanceExtra = ProvenanceExtra; @@ -967,9 +971,20 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { ) }); let buffer_alloc = ecx.machine.weak_memory.then(weak_memory::AllocState::new_allocation); + + // If an allocation is leaked, we want to report a backtrace to indicate where it was + // allocated. We don't need to record a backtrace for allocations which are allowed to + // leak. + let backtrace = if kind.may_leak() { None } else { Some(ecx.generate_stacktrace()) }; + let alloc: Allocation = alloc.adjust_from_tcx( &ecx.tcx, - AllocExtra { borrow_tracker, data_race: race_alloc, weak_memory: buffer_alloc }, + AllocExtra { + borrow_tracker, + data_race: race_alloc, + weak_memory: buffer_alloc, + backtrace, + }, |ptr| ecx.global_base_pointer(ptr), )?; Ok(Cow::Owned(alloc)) @@ -1049,7 +1064,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { fn before_memory_read( _tcx: TyCtxt<'tcx>, machine: &Self, - alloc_extra: &AllocExtra, + alloc_extra: &AllocExtra<'tcx>, (alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra), range: AllocRange, ) -> InterpResult<'tcx> { @@ -1069,7 +1084,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { fn before_memory_write( _tcx: TyCtxt<'tcx>, machine: &mut Self, - alloc_extra: &mut AllocExtra, + alloc_extra: &mut AllocExtra<'tcx>, (alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra), range: AllocRange, ) -> InterpResult<'tcx> { @@ -1089,7 +1104,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { fn before_memory_deallocation( _tcx: TyCtxt<'tcx>, machine: &mut Self, - alloc_extra: &mut AllocExtra, + alloc_extra: &mut AllocExtra<'tcx>, (alloc_id, prove_extra): (AllocId, Self::ProvenanceExtra), range: AllocRange, ) -> InterpResult<'tcx> { diff --git a/src/tools/miri/src/tag_gc.rs b/src/tools/miri/src/tag_gc.rs index c1194fe22163a..cefdcc2b5b83d 100644 --- a/src/tools/miri/src/tag_gc.rs +++ b/src/tools/miri/src/tag_gc.rs @@ -125,7 +125,7 @@ impl VisitTags for Operand { } } -impl VisitTags for Allocation { +impl VisitTags for Allocation> { fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) { for prov in self.provenance().provenances() { prov.visit_tags(visit); diff --git a/src/tools/miri/tests/fail/memleak.rs b/src/tools/miri/tests/fail/memleak.rs index d384caf81a57e..cbeb163b56c31 100644 --- a/src/tools/miri/tests/fail/memleak.rs +++ b/src/tools/miri/tests/fail/memleak.rs @@ -1,4 +1,4 @@ -//@error-pattern: the evaluated program leaked memory +//@error-pattern: memory leaked //@normalize-stderr-test: ".*│.*" -> "$$stripped$$" fn main() { diff --git a/src/tools/miri/tests/fail/memleak.stderr b/src/tools/miri/tests/fail/memleak.stderr index f8b62af3eb857..9b23a71f5abb7 100644 --- a/src/tools/miri/tests/fail/memleak.stderr +++ b/src/tools/miri/tests/fail/memleak.stderr @@ -1,10 +1,21 @@ -The following memory was leaked: ALLOC (Rust heap, size: 4, align: 4) { -$stripped$ -} +error: memory leaked: ALLOC (Rust heap, size: 4, align: 4), allocated here: + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_alloc(layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `::allocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `alloc::alloc::exchange_malloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::boxed::Box::::new` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `main` + --> $DIR/memleak.rs:LL:CC + | +LL | std::mem::forget(Box::new(42)); + | ^^^^^^^^^^^^ -error: the evaluated program leaked memory - -note: pass `-Zmiri-ignore-leaks` to disable this check +note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check error: aborting due to previous error diff --git a/src/tools/miri/tests/fail/memleak_rc.32bit.stderr b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr index da222609091ab..0eaf84048e0a3 100644 --- a/src/tools/miri/tests/fail/memleak_rc.32bit.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr @@ -1,10 +1,22 @@ -The following memory was leaked: ALLOC (Rust heap, size: 16, align: 4) { -$stripped$ -} +error: memory leaked: ALLOC (Rust heap, size: 16, align: 4), allocated here: + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_alloc(layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `::allocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `alloc::alloc::exchange_malloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::boxed::Box::>>>::new` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::rc::Rc::>>::new` at RUSTLIB/alloc/src/rc.rs:LL:CC +note: inside `main` + --> $DIR/memleak_rc.rs:LL:CC + | +LL | let x = Dummy(Rc::new(RefCell::new(None))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: the evaluated program leaked memory - -note: pass `-Zmiri-ignore-leaks` to disable this check +note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check error: aborting due to previous error diff --git a/src/tools/miri/tests/fail/memleak_rc.64bit.stderr b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr index 8c24bbc779bd6..9b7adc00ef5cd 100644 --- a/src/tools/miri/tests/fail/memleak_rc.64bit.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr @@ -1,11 +1,22 @@ -The following memory was leaked: ALLOC (Rust heap, size: 32, align: 8) { -$stripped$ -$stripped$ -} +error: memory leaked: ALLOC (Rust heap, size: 32, align: 8), allocated here: + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_alloc(layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `::allocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `alloc::alloc::exchange_malloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `std::boxed::Box::>>>::new` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::rc::Rc::>>::new` at RUSTLIB/alloc/src/rc.rs:LL:CC +note: inside `main` + --> $DIR/memleak_rc.rs:LL:CC + | +LL | let x = Dummy(Rc::new(RefCell::new(None))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: the evaluated program leaked memory - -note: pass `-Zmiri-ignore-leaks` to disable this check +note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check error: aborting due to previous error diff --git a/src/tools/miri/tests/fail/memleak_rc.rs b/src/tools/miri/tests/fail/memleak_rc.rs index 76ecd71b011aa..cf4671912ada8 100644 --- a/src/tools/miri/tests/fail/memleak_rc.rs +++ b/src/tools/miri/tests/fail/memleak_rc.rs @@ -1,4 +1,4 @@ -//@error-pattern: the evaluated program leaked memory +//@error-pattern: memory leaked //@stderr-per-bitwidth //@normalize-stderr-test: ".*│.*" -> "$$stripped$$" From cbc7f94f113f7ae97db7e04b7f71b753c8349435 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Mon, 10 Apr 2023 22:37:11 -0400 Subject: [PATCH 2/3] Add a flag to disable leak backtraces --- src/tools/miri/README.md | 14 +++++++++----- src/tools/miri/src/bin/miri.rs | 3 +++ src/tools/miri/src/eval.rs | 14 +++++++++++--- src/tools/miri/src/machine.rs | 13 ++++++++++++- src/tools/miri/tests/fail/memleak.stderr | 2 ++ src/tools/miri/tests/fail/memleak_no_backtrace.rs | 7 +++++++ .../miri/tests/fail/memleak_no_backtrace.stderr | 4 ++++ src/tools/miri/tests/fail/memleak_rc.32bit.stderr | 2 ++ src/tools/miri/tests/fail/memleak_rc.64bit.stderr | 2 ++ 9 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 src/tools/miri/tests/fail/memleak_no_backtrace.rs create mode 100644 src/tools/miri/tests/fail/memleak_no_backtrace.stderr diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 4c7351879877e..04a1d939d2e5e 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -301,6 +301,15 @@ environment variable. We first document the most relevant and most commonly used * `-Zmiri-disable-isolation` disables host isolation. As a consequence, the program has access to host resources such as environment variables, file systems, and randomness. +* `-Zmiri-disable-leak-backtraces` disables backtraces reports for memory leaks. By default, a + backtrace is captured for every allocation when it is created, just in case it leaks. This incurs + some memory overhead to store data that is almost never used. This flag is implied by + `-Zmiri-ignore-leaks`. +* `-Zmiri-env-forward=` forwards the `var` environment variable to the interpreted program. Can + be used multiple times to forward several variables. Execution will still be deterministic if the + value of forwarded variables stays the same. Has no effect if `-Zmiri-disable-isolation` is set. +* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some + remaining threads to exist when the main thread exits. * `-Zmiri-isolation-error=` configures Miri's response to operations requiring host access while isolation is enabled. `abort`, `hide`, `warn`, and `warn-nobacktrace` are the supported actions. The default is to `abort`, @@ -308,11 +317,6 @@ environment variable. We first document the most relevant and most commonly used execution with a "permission denied" error being returned to the program. `warn` prints a full backtrace when that happens; `warn-nobacktrace` is less verbose. `hide` hides the warning entirely. -* `-Zmiri-env-forward=` forwards the `var` environment variable to the interpreted program. Can - be used multiple times to forward several variables. Execution will still be deterministic if the - value of forwarded variables stays the same. Has no effect if `-Zmiri-disable-isolation` is set. -* `-Zmiri-ignore-leaks` disables the memory leak checker, and also allows some - remaining threads to exist when the main thread exits. * `-Zmiri-num-cpus` states the number of available CPUs to be reported by miri. By default, the number of available CPUs is `1`. Note that this flag does not affect how miri handles threads in any way. diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 26a7ead2407cb..3aa71bb7e3c87 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -359,6 +359,8 @@ fn main() { isolation_enabled = Some(false); } miri_config.isolated_op = miri::IsolatedOp::Allow; + } else if arg == "-Zmiri-disable-leak-backtraces" { + miri_config.collect_leak_backtraces = false; } else if arg == "-Zmiri-disable-weak-memory-emulation" { miri_config.weak_memory_emulation = false; } else if arg == "-Zmiri-track-weak-memory-loads" { @@ -385,6 +387,7 @@ fn main() { }; } else if arg == "-Zmiri-ignore-leaks" { miri_config.ignore_leaks = true; + miri_config.collect_leak_backtraces = false; } else if arg == "-Zmiri-panic-on-unsupported" { miri_config.panic_on_unsupported = true; } else if arg == "-Zmiri-tag-raw-pointers" { diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 60533687bed57..defd37c37757e 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -146,6 +146,8 @@ pub struct MiriConfig { pub num_cpus: u32, /// Requires Miri to emulate pages of a certain size pub page_size: Option, + /// Whether to collect a backtrace when each allocation is created, just in case it leaks. + pub collect_leak_backtraces: bool, } impl Default for MiriConfig { @@ -180,6 +182,7 @@ impl Default for MiriConfig { gc_interval: 10_000, num_cpus: 1, page_size: None, + collect_leak_backtraces: true, } } } @@ -461,9 +464,14 @@ pub fn eval_entry<'tcx>( let leaks = ecx.find_leaked_allocations(&ecx.machine.static_roots); if !leaks.is_empty() { report_leaks(&ecx, leaks); - tcx.sess.note_without_error( - "the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check", - ); + let leak_message = "the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check"; + if ecx.machine.collect_leak_backtraces { + // If we are collecting leak backtraces, each leak is a distinct error diagnostic. + tcx.sess.note_without_error(leak_message); + } else { + // If we do not have backtraces, we just report an error without any span. + tcx.sess.err(leak_message); + }; // Ignore the provided return code - let the reported error // determine the return code. return None; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index a9c1357fcf298..37f54a4a5bd4b 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -471,12 +471,17 @@ pub struct MiriMachine<'mir, 'tcx> { pub(crate) gc_interval: u32, /// The number of blocks that passed since the last BorTag GC pass. pub(crate) since_gc: u32, + /// The number of CPUs to be reported by miri. pub(crate) num_cpus: u32, + /// Determines Miri's page size and associated values pub(crate) page_size: u64, pub(crate) stack_addr: u64, pub(crate) stack_size: u64, + + /// Whether to collect a backtrace when each allocation is created, just in case it leaks. + pub(crate) collect_leak_backtraces: bool, } impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { @@ -585,6 +590,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { page_size, stack_addr, stack_size, + collect_leak_backtraces: config.collect_leak_backtraces, } } @@ -732,6 +738,7 @@ impl VisitTags for MiriMachine<'_, '_> { page_size: _, stack_addr: _, stack_size: _, + collect_leak_backtraces: _, } = self; threads.visit_tags(visit); @@ -975,7 +982,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { // If an allocation is leaked, we want to report a backtrace to indicate where it was // allocated. We don't need to record a backtrace for allocations which are allowed to // leak. - let backtrace = if kind.may_leak() { None } else { Some(ecx.generate_stacktrace()) }; + let backtrace = if kind.may_leak() || !ecx.machine.collect_leak_backtraces { + None + } else { + Some(ecx.generate_stacktrace()) + }; let alloc: Allocation = alloc.adjust_from_tcx( &ecx.tcx, diff --git a/src/tools/miri/tests/fail/memleak.stderr b/src/tools/miri/tests/fail/memleak.stderr index 9b23a71f5abb7..6d9b664c8f483 100644 --- a/src/tools/miri/tests/fail/memleak.stderr +++ b/src/tools/miri/tests/fail/memleak.stderr @@ -15,6 +15,8 @@ note: inside `main` LL | std::mem::forget(Box::new(42)); | ^^^^^^^^^^^^ +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check error: aborting due to previous error diff --git a/src/tools/miri/tests/fail/memleak_no_backtrace.rs b/src/tools/miri/tests/fail/memleak_no_backtrace.rs new file mode 100644 index 0000000000000..24d4a02df712c --- /dev/null +++ b/src/tools/miri/tests/fail/memleak_no_backtrace.rs @@ -0,0 +1,7 @@ +//@compile-flags: -Zmiri-disable-leak-backtraces +//@error-pattern: the evaluated program leaked memory +//@normalize-stderr-test: ".*│.*" -> "$$stripped$$" + +fn main() { + std::mem::forget(Box::new(42)); +} diff --git a/src/tools/miri/tests/fail/memleak_no_backtrace.stderr b/src/tools/miri/tests/fail/memleak_no_backtrace.stderr new file mode 100644 index 0000000000000..f44e6ce079779 --- /dev/null +++ b/src/tools/miri/tests/fail/memleak_no_backtrace.stderr @@ -0,0 +1,4 @@ +error: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/memleak_rc.32bit.stderr b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr index 0eaf84048e0a3..0e1146cf4ad93 100644 --- a/src/tools/miri/tests/fail/memleak_rc.32bit.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr @@ -16,6 +16,8 @@ note: inside `main` LL | let x = Dummy(Rc::new(RefCell::new(None))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check error: aborting due to previous error diff --git a/src/tools/miri/tests/fail/memleak_rc.64bit.stderr b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr index 9b7adc00ef5cd..4979588f370ff 100644 --- a/src/tools/miri/tests/fail/memleak_rc.64bit.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr @@ -16,6 +16,8 @@ note: inside `main` LL | let x = Dummy(Rc::new(RefCell::new(None))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check error: aborting due to previous error From fb68292b24e5bd14c04d820fd352722cc060c789 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Mon, 10 Apr 2023 22:38:29 -0400 Subject: [PATCH 3/3] Improve doc comment of AllocExtra's backtrace Co-authored-by: Ralf Jung --- src/tools/miri/src/machine.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 37f54a4a5bd4b..ecb3e13dd54e0 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -264,7 +264,8 @@ pub struct AllocExtra<'tcx> { pub weak_memory: Option, /// A backtrace to where this allocation was allocated. /// As this is recorded for leak reports, it only exists - /// if this allocation is leakable. + /// if this allocation is leakable. The backtrace is not + /// pruned yet; that should be done before printing it. pub backtrace: Option>>, }