Skip to content

Commit f2c59bd

Browse files
authored
Rollup merge of #70762 - RalfJung:miri-leak-check, r=oli-obk
Miri leak check: memory reachable through globals is not leaked Also make Miri memory dump prettier by sharing more code with MIR dumping, and fix a bug where the Miri memory dump would print some allocations twice. r? @oli-obk Miri PR: rust-lang/miri#1301
2 parents 795bc2c + 1f3e247 commit f2c59bd

File tree

3 files changed

+116
-76
lines changed

3 files changed

+116
-76
lines changed

src/librustc_mir/interpret/machine.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub trait AllocMap<K: Hash + Eq, V> {
5151
where
5252
K: Borrow<Q>;
5353

54-
/// Returns data based the keys and values in the map.
54+
/// Returns data based on the keys and values in the map.
5555
fn filter_map_collect<T>(&self, f: impl FnMut(&K, &V) -> Option<T>) -> Vec<T>;
5656

5757
/// Returns a reference to entry `k`. If no such entry exists, call
@@ -79,7 +79,7 @@ pub trait AllocMap<K: Hash + Eq, V> {
7979
/// and some use case dependent behaviour can instead be applied.
8080
pub trait Machine<'mir, 'tcx>: Sized {
8181
/// Additional memory kinds a machine wishes to distinguish from the builtin ones
82-
type MemoryKind: ::std::fmt::Debug + MayLeak + Eq + 'static;
82+
type MemoryKind: ::std::fmt::Debug + ::std::fmt::Display + MayLeak + Eq + 'static;
8383

8484
/// Tag tracked alongside every pointer. This is used to implement "Stacked Borrows"
8585
/// <https://www.ralfj.de/blog/2018/08/07/stacked-borrows.html>.

src/librustc_mir/interpret/memory.rs

+70-48
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use std::borrow::Cow;
1010
use std::collections::VecDeque;
1111
use std::convert::TryFrom;
12+
use std::fmt;
1213
use std::ptr;
1314

1415
use rustc_ast::ast::Mutability;
@@ -20,6 +21,7 @@ use super::{
2021
AllocId, AllocMap, Allocation, AllocationExtra, CheckInAllocMsg, ErrorHandled, GlobalAlloc,
2122
GlobalId, InterpResult, Machine, MayLeak, Pointer, PointerArithmetic, Scalar,
2223
};
24+
use crate::util::pretty;
2325

2426
#[derive(Debug, PartialEq, Copy, Clone)]
2527
pub enum MemoryKind<T> {
@@ -45,6 +47,17 @@ impl<T: MayLeak> MayLeak for MemoryKind<T> {
4547
}
4648
}
4749

50+
impl<T: fmt::Display> fmt::Display for MemoryKind<T> {
51+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52+
match self {
53+
MemoryKind::Stack => write!(f, "stack variable"),
54+
MemoryKind::Vtable => write!(f, "vtable"),
55+
MemoryKind::CallerLocation => write!(f, "caller location"),
56+
MemoryKind::Machine(m) => write!(f, "{}", m),
57+
}
58+
}
59+
}
60+
4861
/// Used by `get_size_and_align` to indicate whether the allocation needs to be live.
4962
#[derive(Debug, Copy, Clone)]
5063
pub enum AllocCheck {
@@ -258,7 +271,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
258271

259272
if alloc_kind != kind {
260273
throw_ub_format!(
261-
"deallocating `{:?}` memory using `{:?}` deallocation operation",
274+
"deallocating {} memory using {} deallocation operation",
262275
alloc_kind,
263276
kind
264277
);
@@ -644,81 +657,90 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
644657
self.dump_allocs(vec![id]);
645658
}
646659

647-
fn dump_alloc_helper<Tag, Extra>(
648-
&self,
649-
allocs_seen: &mut FxHashSet<AllocId>,
650-
allocs_to_print: &mut VecDeque<AllocId>,
651-
alloc: &Allocation<Tag, Extra>,
652-
) {
653-
for &(_, (_, target_id)) in alloc.relocations().iter() {
654-
if allocs_seen.insert(target_id) {
655-
allocs_to_print.push_back(target_id);
656-
}
657-
}
658-
crate::util::pretty::write_allocation(self.tcx.tcx, alloc, &mut std::io::stderr(), "")
659-
.unwrap();
660-
}
661-
662660
/// Print a list of allocations and all allocations they point to, recursively.
663661
/// This prints directly to stderr, ignoring RUSTC_LOG! It is up to the caller to
664662
/// control for this.
665663
pub fn dump_allocs(&self, mut allocs: Vec<AllocId>) {
664+
// Cannot be a closure because it is generic in `Tag`, `Extra`.
665+
fn write_allocation_track_relocs<'tcx, Tag, Extra>(
666+
tcx: TyCtxtAt<'tcx>,
667+
allocs_to_print: &mut VecDeque<AllocId>,
668+
alloc: &Allocation<Tag, Extra>,
669+
) {
670+
for &(_, target_id) in alloc.relocations().values() {
671+
allocs_to_print.push_back(target_id);
672+
}
673+
pretty::write_allocation(tcx.tcx, alloc, &mut std::io::stderr()).unwrap();
674+
}
675+
666676
allocs.sort();
667677
allocs.dedup();
668678
let mut allocs_to_print = VecDeque::from(allocs);
669-
let mut allocs_seen = FxHashSet::default();
679+
// `allocs_printed` contains all allocations that we have already printed.
680+
let mut allocs_printed = FxHashSet::default();
670681

671682
while let Some(id) = allocs_to_print.pop_front() {
672-
eprint!("Alloc {:<5}: ", id);
673-
fn msg<Tag, Extra>(alloc: &Allocation<Tag, Extra>, extra: &str) {
674-
eprintln!(
675-
"({} bytes, alignment {}){}",
676-
alloc.size.bytes(),
677-
alloc.align.bytes(),
678-
extra
679-
)
680-
};
683+
if !allocs_printed.insert(id) {
684+
// Already printed, so skip this.
685+
continue;
686+
}
681687

682-
// normal alloc?
683-
match self.alloc_map.get_or(id, || Err(())) {
684-
Ok((kind, alloc)) => {
685-
match kind {
686-
MemoryKind::Stack => msg(alloc, " (stack)"),
687-
MemoryKind::Vtable => msg(alloc, " (vtable)"),
688-
MemoryKind::CallerLocation => msg(alloc, " (caller_location)"),
689-
MemoryKind::Machine(m) => msg(alloc, &format!(" ({:?})", m)),
690-
};
691-
self.dump_alloc_helper(&mut allocs_seen, &mut allocs_to_print, alloc);
688+
eprint!("{}", id);
689+
match self.alloc_map.get(id) {
690+
Some(&(kind, ref alloc)) => {
691+
// normal alloc
692+
eprint!(" ({}, ", kind);
693+
write_allocation_track_relocs(self.tcx, &mut allocs_to_print, alloc);
692694
}
693-
Err(()) => {
694-
// global alloc?
695+
None => {
696+
// global alloc
695697
match self.tcx.alloc_map.lock().get(id) {
696698
Some(GlobalAlloc::Memory(alloc)) => {
697-
msg(alloc, " (immutable)");
698-
self.dump_alloc_helper(&mut allocs_seen, &mut allocs_to_print, alloc);
699+
eprint!(" (unchanged global, ");
700+
write_allocation_track_relocs(self.tcx, &mut allocs_to_print, alloc);
699701
}
700702
Some(GlobalAlloc::Function(func)) => {
701-
eprintln!("{}", func);
703+
eprint!(" (fn: {})", func);
702704
}
703705
Some(GlobalAlloc::Static(did)) => {
704-
eprintln!("{:?}", did);
706+
eprint!(" (static: {})", self.tcx.def_path_str(did));
705707
}
706708
None => {
707-
eprintln!("(deallocated)");
709+
eprint!(" (deallocated)");
708710
}
709711
}
710712
}
711-
};
713+
}
714+
eprintln!();
712715
}
713716
}
714717

715718
pub fn leak_report(&self) -> usize {
716-
let leaks: Vec<_> = self
717-
.alloc_map
718-
.filter_map_collect(|&id, &(kind, _)| if kind.may_leak() { None } else { Some(id) });
719+
// Collect the set of allocations that are *reachable* from `Global` allocations.
720+
let reachable = {
721+
let mut reachable = FxHashSet::default();
722+
let global_kind = M::GLOBAL_KIND.map(MemoryKind::Machine);
723+
let mut todo: Vec<_> = self.alloc_map.filter_map_collect(move |&id, &(kind, _)| {
724+
if Some(kind) == global_kind { Some(id) } else { None }
725+
});
726+
while let Some(id) = todo.pop() {
727+
if reachable.insert(id) {
728+
// This is a new allocation, add its relocations to `todo`.
729+
if let Some((_, alloc)) = self.alloc_map.get(id) {
730+
todo.extend(alloc.relocations().values().map(|&(_, target_id)| target_id));
731+
}
732+
}
733+
}
734+
reachable
735+
};
736+
737+
// All allocations that are *not* `reachable` and *not* `may_leak` are considered leaking.
738+
let leaks: Vec<_> = self.alloc_map.filter_map_collect(|&id, &(kind, _)| {
739+
if kind.may_leak() || reachable.contains(&id) { None } else { Some(id) }
740+
});
719741
let n = leaks.len();
720742
if n > 0 {
721-
eprintln!("### LEAK REPORT ###");
743+
eprintln!("The following memory was leaked:");
722744
self.dump_allocs(leaks);
723745
}
724746
n

src/librustc_mir/util/pretty.rs

+44-26
Original file line numberDiff line numberDiff line change
@@ -567,26 +567,21 @@ pub fn write_allocations<'tcx>(
567567
}
568568
let mut visitor = CollectAllocIds(Default::default());
569569
body.visit_with(&mut visitor);
570+
// `seen` contains all seen allocations, including the ones we have *not* printed yet.
571+
// The protocol is to first `insert` into `seen`, and only if that returns `true`
572+
// then push to `todo`.
570573
let mut seen = visitor.0;
571574
let mut todo: Vec<_> = seen.iter().copied().collect();
572575
while let Some(id) = todo.pop() {
573-
let mut write_header_and_allocation =
576+
let mut write_allocation_track_relocs =
574577
|w: &mut dyn Write, alloc: &Allocation| -> io::Result<()> {
575-
write!(w, "size: {}, align: {})", alloc.size.bytes(), alloc.align.bytes())?;
576-
if alloc.size == Size::ZERO {
577-
write!(w, " {{}}")?;
578-
} else {
579-
writeln!(w, " {{")?;
580-
write_allocation(tcx, alloc, w, " ")?;
581-
write!(w, "}}")?;
582-
// `.rev()` because we are popping them from the back of the `todo` vector.
583-
for id in alloc_ids_from_alloc(alloc).rev() {
584-
if seen.insert(id) {
585-
todo.push(id);
586-
}
578+
// `.rev()` because we are popping them from the back of the `todo` vector.
579+
for id in alloc_ids_from_alloc(alloc).rev() {
580+
if seen.insert(id) {
581+
todo.push(id);
587582
}
588583
}
589-
Ok(())
584+
write_allocation(tcx, alloc, w)
590585
};
591586
write!(w, "\n{}", id)?;
592587
let alloc = tcx.alloc_map.lock().get(id);
@@ -599,7 +594,7 @@ pub fn write_allocations<'tcx>(
599594
match tcx.const_eval_poly(did) {
600595
Ok(ConstValue::ByRef { alloc, .. }) => {
601596
write!(w, " (static: {}, ", tcx.def_path_str(did))?;
602-
write_header_and_allocation(w, alloc)?;
597+
write_allocation_track_relocs(w, alloc)?;
603598
}
604599
Ok(_) => {
605600
span_bug!(tcx.def_span(did), " static item without `ByRef` initializer")
@@ -616,15 +611,46 @@ pub fn write_allocations<'tcx>(
616611
}
617612
Some(GlobalAlloc::Memory(alloc)) => {
618613
write!(w, " (")?;
619-
write_header_and_allocation(w, alloc)?
614+
write_allocation_track_relocs(w, alloc)?
620615
}
621616
}
622-
623617
writeln!(w)?;
624618
}
625619
Ok(())
626620
}
627621

622+
/// Dumps the size and metadata and content of an allocation to the given writer.
623+
/// The expectation is that the caller first prints other relevant metadata, so the exact
624+
/// format of this function is (*without* leading or trailing newline):
625+
/// ```
626+
/// size: {}, align: {}) {
627+
/// <bytes>
628+
/// }
629+
/// ```
630+
///
631+
/// The byte format is similar to how hex editors print bytes. Each line starts with the address of
632+
/// the start of the line, followed by all bytes in hex format (space separated).
633+
/// If the allocation is small enough to fit into a single line, no start address is given.
634+
/// After the hex dump, an ascii dump follows, replacing all unprintable characters (control
635+
/// characters or characters whose value is larger than 127) with a `.`
636+
/// This also prints relocations adequately.
637+
pub fn write_allocation<Tag, Extra>(
638+
tcx: TyCtxt<'tcx>,
639+
alloc: &Allocation<Tag, Extra>,
640+
w: &mut dyn Write,
641+
) -> io::Result<()> {
642+
write!(w, "size: {}, align: {})", alloc.size.bytes(), alloc.align.bytes())?;
643+
if alloc.size == Size::ZERO {
644+
// We are done.
645+
return write!(w, " {{}}");
646+
}
647+
// Write allocation bytes.
648+
writeln!(w, " {{")?;
649+
write_allocation_bytes(tcx, alloc, w, " ")?;
650+
write!(w, "}}")?;
651+
Ok(())
652+
}
653+
628654
fn write_allocation_endline(w: &mut dyn Write, ascii: &str) -> io::Result<()> {
629655
for _ in 0..(BYTES_PER_LINE - ascii.chars().count()) {
630656
write!(w, " ")?;
@@ -649,18 +675,10 @@ fn write_allocation_newline(
649675
Ok(line_start)
650676
}
651677

652-
/// Dumps the bytes of an allocation to the given writer. This also prints relocations instead of
653-
/// the raw bytes where applicable.
654-
/// The byte format is similar to how hex editors print bytes. Each line starts with the address of
655-
/// the start of the line, followed by all bytes in hex format (space separated).
656-
/// If the allocation is small enough to fit into a single line, no start address is given.
657-
/// After the hex dump, an ascii dump follows, replacing all unprintable characters (control
658-
/// characters or characters whose value is larger than 127) with a `.`
659-
///
660678
/// The `prefix` argument allows callers to add an arbitrary prefix before each line (even if there
661679
/// is only one line). Note that your prefix should contain a trailing space as the lines are
662680
/// printed directly after it.
663-
pub fn write_allocation<Tag, Extra>(
681+
fn write_allocation_bytes<Tag, Extra>(
664682
tcx: TyCtxt<'tcx>,
665683
alloc: &Allocation<Tag, Extra>,
666684
w: &mut dyn Write,

0 commit comments

Comments
 (0)