Auto merge of #109061 - saethlin:leak-backtraces, r=oli-obk
Add a backtrace to Allocation, display it in leak reports This addresses https://github.com/rust-lang/miri/issues/2813 Information like this from diagnostics is indispensable for diagnosing problems that are difficult to reproduce such as https://github.com/rust-lang/miri-test-libstd/actions/runs/4395316008/jobs/7697019211#step:4:770 (which has not been reproduced or diagnosed).
This commit is contained in:
commit
23eb90ffa7
17 changed files with 191 additions and 62 deletions
|
@ -132,11 +132,10 @@ pub struct Frame<'mir, 'tcx, Prov: Provenance = AllocId, Extra = ()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What we store about a frame in an interpreter backtrace.
|
/// What we store about a frame in an interpreter backtrace.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FrameInfo<'tcx> {
|
pub struct FrameInfo<'tcx> {
|
||||||
pub instance: ty::Instance<'tcx>,
|
pub instance: ty::Instance<'tcx>,
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub lint_root: Option<hir::HirId>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)] // Miri debug-prints these
|
#[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
|
// This deliberately does *not* honor `requires_caller_location` since it is used for much
|
||||||
// more than just panics.
|
// more than just panics.
|
||||||
for frame in stack.iter().rev() {
|
for frame in stack.iter().rev() {
|
||||||
let lint_root = frame.lint_root();
|
|
||||||
let span = frame.current_span();
|
let span = frame.current_span();
|
||||||
|
frames.push(FrameInfo { span, instance: frame.instance });
|
||||||
frames.push(FrameInfo { span, instance: frame.instance, lint_root });
|
|
||||||
}
|
}
|
||||||
trace!("generate stacktrace: {:#?}", frames);
|
trace!("generate stacktrace: {:#?}", frames);
|
||||||
frames
|
frames
|
||||||
|
|
|
@ -104,7 +104,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
|
||||||
type FrameExtra;
|
type FrameExtra;
|
||||||
|
|
||||||
/// Extra data stored in every allocation.
|
/// Extra data stored in every allocation.
|
||||||
type AllocExtra: Debug + Clone + 'static;
|
type AllocExtra: Debug + Clone + 'tcx;
|
||||||
|
|
||||||
/// Type for the bytes of the allocation.
|
/// Type for the bytes of the allocation.
|
||||||
type Bytes: AllocBytes + 'static;
|
type Bytes: AllocBytes + 'static;
|
||||||
|
|
|
@ -215,7 +215,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
self.allocate_raw_ptr(alloc, kind)
|
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(
|
pub fn allocate_raw_ptr(
|
||||||
&mut self,
|
&mut self,
|
||||||
alloc: Allocation,
|
alloc: Allocation,
|
||||||
|
@ -807,9 +807,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
DumpAllocs { ecx: self, allocs }
|
DumpAllocs { ecx: self, allocs }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print leaked memory. Allocations reachable from `static_roots` or a `Global` allocation
|
/// Find leaked allocations. Allocations reachable from `static_roots` or a `Global` allocation
|
||||||
/// are not considered leaked. Leaks whose kind `may_leak()` returns true are not reported.
|
/// are not considered leaked, as well as leaks whose kind's `may_leak()` returns true.
|
||||||
pub fn leak_report(&self, static_roots: &[AllocId]) -> usize {
|
pub fn find_leaked_allocations(
|
||||||
|
&self,
|
||||||
|
static_roots: &[AllocId],
|
||||||
|
) -> Vec<(AllocId, MemoryKind<M::MemoryKind>, Allocation<M::Provenance, M::AllocExtra, M::Bytes>)>
|
||||||
|
{
|
||||||
// Collect the set of allocations that are *reachable* from `Global` allocations.
|
// Collect the set of allocations that are *reachable* from `Global` allocations.
|
||||||
let reachable = {
|
let reachable = {
|
||||||
let mut reachable = FxHashSet::default();
|
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.
|
// 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, _)| {
|
self.memory.alloc_map.filter_map_collect(|id, (kind, alloc)| {
|
||||||
if kind.may_leak() || reachable.contains(&id) { None } else { Some(id) }
|
if kind.may_leak() || reachable.contains(id) {
|
||||||
});
|
None
|
||||||
let n = leaks.len();
|
} else {
|
||||||
if n > 0 {
|
Some((*id, *kind, alloc.clone()))
|
||||||
eprintln!("The following memory was leaked: {:?}", self.dump_allocs(leaks));
|
}
|
||||||
}
|
})
|
||||||
n
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
* `-Zmiri-disable-isolation` disables host isolation. As a consequence,
|
||||||
the program has access to host resources such as environment variables, file
|
the program has access to host resources such as environment variables, file
|
||||||
systems, and randomness.
|
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=<var>` 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=<action>` configures Miri's response to operations
|
* `-Zmiri-isolation-error=<action>` configures Miri's response to operations
|
||||||
requiring host access while isolation is enabled. `abort`, `hide`, `warn`,
|
requiring host access while isolation is enabled. `abort`, `hide`, `warn`,
|
||||||
and `warn-nobacktrace` are the supported actions. The default is to `abort`,
|
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.
|
execution with a "permission denied" error being returned to the program.
|
||||||
`warn` prints a full backtrace when that happens; `warn-nobacktrace` is less
|
`warn` prints a full backtrace when that happens; `warn-nobacktrace` is less
|
||||||
verbose. `hide` hides the warning entirely.
|
verbose. `hide` hides the warning entirely.
|
||||||
* `-Zmiri-env-forward=<var>` 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
|
* `-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
|
number of available CPUs is `1`. Note that this flag does not affect how miri handles threads in
|
||||||
any way.
|
any way.
|
||||||
|
|
|
@ -359,6 +359,8 @@ fn main() {
|
||||||
isolation_enabled = Some(false);
|
isolation_enabled = Some(false);
|
||||||
}
|
}
|
||||||
miri_config.isolated_op = miri::IsolatedOp::Allow;
|
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" {
|
} else if arg == "-Zmiri-disable-weak-memory-emulation" {
|
||||||
miri_config.weak_memory_emulation = false;
|
miri_config.weak_memory_emulation = false;
|
||||||
} else if arg == "-Zmiri-track-weak-memory-loads" {
|
} else if arg == "-Zmiri-track-weak-memory-loads" {
|
||||||
|
@ -385,6 +387,7 @@ fn main() {
|
||||||
};
|
};
|
||||||
} else if arg == "-Zmiri-ignore-leaks" {
|
} else if arg == "-Zmiri-ignore-leaks" {
|
||||||
miri_config.ignore_leaks = true;
|
miri_config.ignore_leaks = true;
|
||||||
|
miri_config.collect_leak_backtraces = false;
|
||||||
} else if arg == "-Zmiri-panic-on-unsupported" {
|
} else if arg == "-Zmiri-panic-on-unsupported" {
|
||||||
miri_config.panic_on_unsupported = true;
|
miri_config.panic_on_unsupported = true;
|
||||||
} else if arg == "-Zmiri-tag-raw-pointers" {
|
} else if arg == "-Zmiri-tag-raw-pointers" {
|
||||||
|
|
|
@ -352,7 +352,7 @@ pub enum AllocState {
|
||||||
TreeBorrows(Box<RefCell<tree_borrows::AllocState>>),
|
TreeBorrows(Box<RefCell<tree_borrows::AllocState>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl machine::AllocExtra {
|
impl machine::AllocExtra<'_> {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn borrow_tracker_sb(&self) -> &RefCell<stacked_borrows::AllocState> {
|
pub fn borrow_tracker_sb(&self) -> &RefCell<stacked_borrows::AllocState> {
|
||||||
match self.borrow_tracker {
|
match self.borrow_tracker {
|
||||||
|
|
|
@ -105,7 +105,7 @@ pub enum NonHaltingDiagnostic {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Level of Miri specific diagnostics
|
/// Level of Miri specific diagnostics
|
||||||
enum DiagLevel {
|
pub enum DiagLevel {
|
||||||
Error,
|
Error,
|
||||||
Warning,
|
Warning,
|
||||||
Note,
|
Note,
|
||||||
|
@ -114,7 +114,7 @@ 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<'tcx>(
|
pub fn prune_stacktrace<'tcx>(
|
||||||
mut stacktrace: Vec<FrameInfo<'tcx>>,
|
mut stacktrace: Vec<FrameInfo<'tcx>>,
|
||||||
machine: &MiriMachine<'_, 'tcx>,
|
machine: &MiriMachine<'_, 'tcx>,
|
||||||
) -> (Vec<FrameInfo<'tcx>>, bool) {
|
) -> (Vec<FrameInfo<'tcx>>, bool) {
|
||||||
|
@ -338,12 +338,45 @@ pub fn report_error<'tcx, 'mir>(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn report_leaks<'mir, 'tcx>(
|
||||||
|
ecx: &InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||||
|
leaks: Vec<(AllocId, MemoryKind<MiriMemoryKind>, Allocation<Provenance, AllocExtra<'tcx>>)>,
|
||||||
|
) {
|
||||||
|
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.
|
/// Report an error or note (depending on the `error` argument) with the given stacktrace.
|
||||||
/// Also emits a full stacktrace of the interpreter stack.
|
/// 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
|
/// 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<'tcx>(
|
pub fn report_msg<'tcx>(
|
||||||
diag_level: DiagLevel,
|
diag_level: DiagLevel,
|
||||||
title: &str,
|
title: &str,
|
||||||
span_msg: Vec<String>,
|
span_msg: Vec<String>,
|
||||||
|
|
|
@ -10,6 +10,7 @@ use std::thread;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::borrow_tracker::RetagFields;
|
use crate::borrow_tracker::RetagFields;
|
||||||
|
use crate::diagnostics::report_leaks;
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
use rustc_hir::def::Namespace;
|
use rustc_hir::def::Namespace;
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
|
@ -145,6 +146,8 @@ pub struct MiriConfig {
|
||||||
pub num_cpus: u32,
|
pub num_cpus: u32,
|
||||||
/// Requires Miri to emulate pages of a certain size
|
/// Requires Miri to emulate pages of a certain size
|
||||||
pub page_size: Option<u64>,
|
pub page_size: Option<u64>,
|
||||||
|
/// Whether to collect a backtrace when each allocation is created, just in case it leaks.
|
||||||
|
pub collect_leak_backtraces: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MiriConfig {
|
impl Default for MiriConfig {
|
||||||
|
@ -179,6 +182,7 @@ impl Default for MiriConfig {
|
||||||
gc_interval: 10_000,
|
gc_interval: 10_000,
|
||||||
num_cpus: 1,
|
num_cpus: 1,
|
||||||
page_size: None,
|
page_size: None,
|
||||||
|
collect_leak_backtraces: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -457,10 +461,17 @@ pub fn eval_entry<'tcx>(
|
||||||
}
|
}
|
||||||
// Check for memory leaks.
|
// Check for memory leaks.
|
||||||
info!("Additonal static roots: {:?}", ecx.machine.static_roots);
|
info!("Additonal static roots: {:?}", ecx.machine.static_roots);
|
||||||
let leaks = ecx.leak_report(&ecx.machine.static_roots);
|
let leaks = ecx.find_leaked_allocations(&ecx.machine.static_roots);
|
||||||
if leaks != 0 {
|
if !leaks.is_empty() {
|
||||||
tcx.sess.err("the evaluated program leaked memory");
|
report_leaks(&ecx, leaks);
|
||||||
tcx.sess.note_without_error("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
|
// Ignore the provided return code - let the reported error
|
||||||
// determine the return code.
|
// determine the return code.
|
||||||
return None;
|
return None;
|
||||||
|
|
|
@ -253,20 +253,25 @@ impl ProvenanceExtra {
|
||||||
|
|
||||||
/// Extra per-allocation data
|
/// Extra per-allocation data
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AllocExtra {
|
pub struct AllocExtra<'tcx> {
|
||||||
/// Global state of the borrow tracker, if enabled.
|
/// Global state of the borrow tracker, if enabled.
|
||||||
pub borrow_tracker: Option<borrow_tracker::AllocState>,
|
pub borrow_tracker: Option<borrow_tracker::AllocState>,
|
||||||
/// Data race detection via the use of a vector-clock,
|
/// Data race detection via the use of a vector-clock.
|
||||||
/// this is only added if it is enabled.
|
/// This is only added if it is enabled.
|
||||||
pub data_race: Option<data_race::AllocState>,
|
pub data_race: Option<data_race::AllocState>,
|
||||||
/// Weak memory emulation via the use of store buffers,
|
/// Weak memory emulation via the use of store buffers.
|
||||||
/// this is only added if it is enabled.
|
/// This is only added if it is enabled.
|
||||||
pub weak_memory: Option<weak_memory::AllocState>,
|
pub weak_memory: Option<weak_memory::AllocState>,
|
||||||
|
/// A backtrace to where this allocation was allocated.
|
||||||
|
/// As this is recorded for leak reports, it only exists
|
||||||
|
/// if this allocation is leakable. The backtrace is not
|
||||||
|
/// pruned yet; that should be done before printing it.
|
||||||
|
pub backtrace: Option<Vec<FrameInfo<'tcx>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for AllocExtra {
|
impl VisitTags for AllocExtra<'_> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
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);
|
borrow_tracker.visit_tags(visit);
|
||||||
data_race.visit_tags(visit);
|
data_race.visit_tags(visit);
|
||||||
|
@ -467,12 +472,17 @@ pub struct MiriMachine<'mir, 'tcx> {
|
||||||
pub(crate) gc_interval: u32,
|
pub(crate) gc_interval: u32,
|
||||||
/// The number of blocks that passed since the last BorTag GC pass.
|
/// The number of blocks that passed since the last BorTag GC pass.
|
||||||
pub(crate) since_gc: u32,
|
pub(crate) since_gc: u32,
|
||||||
|
|
||||||
/// The number of CPUs to be reported by miri.
|
/// The number of CPUs to be reported by miri.
|
||||||
pub(crate) num_cpus: u32,
|
pub(crate) num_cpus: u32,
|
||||||
|
|
||||||
/// Determines Miri's page size and associated values
|
/// Determines Miri's page size and associated values
|
||||||
pub(crate) page_size: u64,
|
pub(crate) page_size: u64,
|
||||||
pub(crate) stack_addr: u64,
|
pub(crate) stack_addr: u64,
|
||||||
pub(crate) stack_size: 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> {
|
impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
|
||||||
|
@ -581,6 +591,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
|
||||||
page_size,
|
page_size,
|
||||||
stack_addr,
|
stack_addr,
|
||||||
stack_size,
|
stack_size,
|
||||||
|
collect_leak_backtraces: config.collect_leak_backtraces,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -728,6 +739,7 @@ impl VisitTags for MiriMachine<'_, '_> {
|
||||||
page_size: _,
|
page_size: _,
|
||||||
stack_addr: _,
|
stack_addr: _,
|
||||||
stack_size: _,
|
stack_size: _,
|
||||||
|
collect_leak_backtraces: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
threads.visit_tags(visit);
|
threads.visit_tags(visit);
|
||||||
|
@ -773,7 +785,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||||
type ExtraFnVal = Dlsym;
|
type ExtraFnVal = Dlsym;
|
||||||
|
|
||||||
type FrameExtra = FrameExtra<'tcx>;
|
type FrameExtra = FrameExtra<'tcx>;
|
||||||
type AllocExtra = AllocExtra;
|
type AllocExtra = AllocExtra<'tcx>;
|
||||||
|
|
||||||
type Provenance = Provenance;
|
type Provenance = Provenance;
|
||||||
type ProvenanceExtra = ProvenanceExtra;
|
type ProvenanceExtra = ProvenanceExtra;
|
||||||
|
@ -967,9 +979,24 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let buffer_alloc = ecx.machine.weak_memory.then(weak_memory::AllocState::new_allocation);
|
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() || !ecx.machine.collect_leak_backtraces {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ecx.generate_stacktrace())
|
||||||
|
};
|
||||||
|
|
||||||
let alloc: Allocation<Provenance, Self::AllocExtra> = alloc.adjust_from_tcx(
|
let alloc: Allocation<Provenance, Self::AllocExtra> = alloc.adjust_from_tcx(
|
||||||
&ecx.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),
|
|ptr| ecx.global_base_pointer(ptr),
|
||||||
)?;
|
)?;
|
||||||
Ok(Cow::Owned(alloc))
|
Ok(Cow::Owned(alloc))
|
||||||
|
@ -1049,7 +1076,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||||
fn before_memory_read(
|
fn before_memory_read(
|
||||||
_tcx: TyCtxt<'tcx>,
|
_tcx: TyCtxt<'tcx>,
|
||||||
machine: &Self,
|
machine: &Self,
|
||||||
alloc_extra: &AllocExtra,
|
alloc_extra: &AllocExtra<'tcx>,
|
||||||
(alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
|
(alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
|
||||||
range: AllocRange,
|
range: AllocRange,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
|
@ -1069,7 +1096,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||||
fn before_memory_write(
|
fn before_memory_write(
|
||||||
_tcx: TyCtxt<'tcx>,
|
_tcx: TyCtxt<'tcx>,
|
||||||
machine: &mut Self,
|
machine: &mut Self,
|
||||||
alloc_extra: &mut AllocExtra,
|
alloc_extra: &mut AllocExtra<'tcx>,
|
||||||
(alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
|
(alloc_id, prov_extra): (AllocId, Self::ProvenanceExtra),
|
||||||
range: AllocRange,
|
range: AllocRange,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
|
@ -1089,7 +1116,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||||
fn before_memory_deallocation(
|
fn before_memory_deallocation(
|
||||||
_tcx: TyCtxt<'tcx>,
|
_tcx: TyCtxt<'tcx>,
|
||||||
machine: &mut Self,
|
machine: &mut Self,
|
||||||
alloc_extra: &mut AllocExtra,
|
alloc_extra: &mut AllocExtra<'tcx>,
|
||||||
(alloc_id, prove_extra): (AllocId, Self::ProvenanceExtra),
|
(alloc_id, prove_extra): (AllocId, Self::ProvenanceExtra),
|
||||||
range: AllocRange,
|
range: AllocRange,
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
|
|
|
@ -125,7 +125,7 @@ impl VisitTags for Operand<Provenance> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisitTags for Allocation<Provenance, AllocExtra> {
|
impl VisitTags for Allocation<Provenance, AllocExtra<'_>> {
|
||||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||||
for prov in self.provenance().provenances() {
|
for prov in self.provenance().provenances() {
|
||||||
prov.visit_tags(visit);
|
prov.visit_tags(visit);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@error-pattern: the evaluated program leaked memory
|
//@error-pattern: memory leaked
|
||||||
//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
|
//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
The following memory was leaked: ALLOC (Rust heap, size: 4, align: 4) {
|
error: memory leaked: ALLOC (Rust heap, size: 4, align: 4), allocated here:
|
||||||
$stripped$
|
--> 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 `<std::alloc::Global as std::alloc::Allocator>::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::<i32>::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: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||||
|
|
||||||
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
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
|
7
src/tools/miri/tests/fail/memleak_no_backtrace.rs
Normal file
7
src/tools/miri/tests/fail/memleak_no_backtrace.rs
Normal file
|
@ -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));
|
||||||
|
}
|
4
src/tools/miri/tests/fail/memleak_no_backtrace.stderr
Normal file
4
src/tools/miri/tests/fail/memleak_no_backtrace.stderr
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
error: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
|
@ -1,10 +1,24 @@
|
||||||
The following memory was leaked: ALLOC (Rust heap, size: 16, align: 4) {
|
error: memory leaked: ALLOC (Rust heap, size: 16, align: 4), allocated here:
|
||||||
$stripped$
|
--> 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 `<std::alloc::Global as std::alloc::Allocator>::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::<std::rc::RcBox<std::cell::RefCell<std::option::Option<Dummy>>>>::new` at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||||
|
= note: inside `std::rc::Rc::<std::cell::RefCell<std::option::Option<Dummy>>>::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: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||||
|
|
||||||
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
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,24 @@
|
||||||
The following memory was leaked: ALLOC (Rust heap, size: 32, align: 8) {
|
error: memory leaked: ALLOC (Rust heap, size: 32, align: 8), allocated here:
|
||||||
$stripped$
|
--> RUSTLIB/alloc/src/alloc.rs:LL:CC
|
||||||
$stripped$
|
|
|
||||||
}
|
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 `<std::alloc::Global as std::alloc::Allocator>::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::<std::rc::RcBox<std::cell::RefCell<std::option::Option<Dummy>>>>::new` at RUSTLIB/alloc/src/boxed.rs:LL:CC
|
||||||
|
= note: inside `std::rc::Rc::<std::cell::RefCell<std::option::Option<Dummy>>>::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: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||||
|
|
||||||
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
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//@error-pattern: the evaluated program leaked memory
|
//@error-pattern: memory leaked
|
||||||
//@stderr-per-bitwidth
|
//@stderr-per-bitwidth
|
||||||
//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
|
//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue