9
9
use std:: borrow:: Cow ;
10
10
use std:: collections:: VecDeque ;
11
11
use std:: convert:: TryFrom ;
12
+ use std:: fmt;
12
13
use std:: ptr;
13
14
14
15
use rustc_ast:: ast:: Mutability ;
@@ -20,6 +21,7 @@ use super::{
20
21
AllocId , AllocMap , Allocation , AllocationExtra , CheckInAllocMsg , ErrorHandled , GlobalAlloc ,
21
22
GlobalId , InterpResult , Machine , MayLeak , Pointer , PointerArithmetic , Scalar ,
22
23
} ;
24
+ use crate :: util:: pretty;
23
25
24
26
#[ derive( Debug , PartialEq , Copy , Clone ) ]
25
27
pub enum MemoryKind < T > {
@@ -45,6 +47,17 @@ impl<T: MayLeak> MayLeak for MemoryKind<T> {
45
47
}
46
48
}
47
49
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
+
48
61
/// Used by `get_size_and_align` to indicate whether the allocation needs to be live.
49
62
#[ derive( Debug , Copy , Clone ) ]
50
63
pub enum AllocCheck {
@@ -258,7 +271,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
258
271
259
272
if alloc_kind != kind {
260
273
throw_ub_format ! (
261
- "deallocating `{:?}` memory using `{:?}` deallocation operation" ,
274
+ "deallocating {} memory using {} deallocation operation" ,
262
275
alloc_kind,
263
276
kind
264
277
) ;
@@ -644,81 +657,90 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
644
657
self . dump_allocs ( vec ! [ id] ) ;
645
658
}
646
659
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
-
662
660
/// Print a list of allocations and all allocations they point to, recursively.
663
661
/// This prints directly to stderr, ignoring RUSTC_LOG! It is up to the caller to
664
662
/// control for this.
665
663
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
+
666
676
allocs. sort ( ) ;
667
677
allocs. dedup ( ) ;
668
678
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 ( ) ;
670
681
671
682
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
+ }
681
687
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) ;
692
694
}
693
- Err ( ( ) ) => {
694
- // global alloc?
695
+ None => {
696
+ // global alloc
695
697
match self . tcx . alloc_map . lock ( ) . get ( id) {
696
698
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) ;
699
701
}
700
702
Some ( GlobalAlloc :: Function ( func) ) => {
701
- eprintln ! ( "{} " , func) ;
703
+ eprint ! ( " (fn: {}) " , func) ;
702
704
}
703
705
Some ( GlobalAlloc :: Static ( did) ) => {
704
- eprintln ! ( "{:?} " , did) ;
706
+ eprint ! ( " (static: {}) " , self . tcx . def_path_str ( did) ) ;
705
707
}
706
708
None => {
707
- eprintln ! ( "(deallocated)" ) ;
709
+ eprint ! ( " (deallocated)" ) ;
708
710
}
709
711
}
710
712
}
711
- } ;
713
+ }
714
+ eprintln ! ( ) ;
712
715
}
713
716
}
714
717
715
718
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
+ } ) ;
719
741
let n = leaks. len ( ) ;
720
742
if n > 0 {
721
- eprintln ! ( "### LEAK REPORT ### " ) ;
743
+ eprintln ! ( "The following memory was leaked: " ) ;
722
744
self . dump_allocs ( leaks) ;
723
745
}
724
746
n
0 commit comments