99use std:: borrow:: Cow ;
1010use std:: collections:: VecDeque ;
1111use std:: convert:: TryFrom ;
12+ use std:: fmt;
1213use std:: ptr;
1314
1415use 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 ) ]
2527pub 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 ) ]
5063pub 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
0 commit comments