File Coverage

blib/lib/Devel/Leak/Object.pm
Criterion Covered Total %
statement 154 227 67.8
branch 64 114 56.1
condition 13 23 56.5
subroutine 17 17 100.0
pod 0 5 0.0
total 248 386 64.2


line stmt bran cond sub pod time code
1             package Devel::Leak::Object;
2              
3 8     8   153800 use 5.005;
  8         24  
  8         1788  
4             # We abuse refs a LOT
5 7     7   41 use strict qw{ vars subs };
  7         15  
  7         1262  
6 4     4   24 use Carp ();
  4         12  
  4         71  
7 4     4   21 use Scalar::Util ();
  4         8  
  4         86  
8              
9 4     4   20 use vars qw{ $VERSION @ISA @EXPORT_OK };
  4         7  
  4         306  
10 4     4   21 use vars qw{ %OBJECT_COUNT %TRACKED %DESTROY_ORIGINAL %DESTROY_STUBBED %DESTROY_NEXT %IGNORE_CLASS %OBJECT_COUNT_CHECKPOINT };
  4         7  
  4         749  
11             BEGIN {
12 4     4   9 $VERSION = '1.01';
13              
14             # Set up exports
15 4         26 require Exporter;
16 4         549 @ISA = qw(Exporter);
17 4         18 @EXPORT_OK = qw(track bless status checkpoint);
18              
19             # Set up state storage (primary for clarity)
20 4         11 %OBJECT_COUNT = ();
21 4         7 %OBJECT_COUNT_CHECKPOINT = ();
22 4         7 %TRACKED = ();
23 4         7 %DESTROY_ORIGINAL = ();
24 4         6 %DESTROY_STUBBED = ();
25 4         59 %DESTROY_NEXT = ();
26 4         6221 %IGNORE_CLASS = ();
27             }
28              
29             sub import {
30 4     4   40 my $class = shift;
31 4         13 my @import = ();
32 4         21 while ( @_ ) {
33 1         1 my $function = shift;
34 1 50       8 unless ( $function =~ /^GLOBAL_(.*)$/ ) {
35 0         0 push @import, $function;
36 0         0 next;
37             }
38 1         4 my $global = $1;
39 1         1 *{'CORE::GLOBAL::' . $global} = \&{$global};
  1         13  
  1         3  
40             }
41 4         2341 return $class->SUPER::import(@import);
42             }
43              
44             sub bless {
45 7     7 0 23645 my $reference = shift;
46 7 100       26 my $class = @_ ? shift : scalar caller;
47 7         24 my $object = CORE::bless($reference, $class);
48 7         17 Devel::Leak::Object::track($object);
49 7         74 return $object;
50             };
51              
52             sub track {
53 15     15 0 4566 my $object = shift;
54 15         83 my $class = Scalar::Util::blessed($object);
55 15 50       51 unless ( defined $class ) {
56 0         0 Carp::carp("Devel::Leak::Object::track was passed a non-object");
57             }
58 15 50       49 return if (defined($IGNORE_CLASS{$class}));
59 15         41 my $address = Scalar::Util::refaddr($object);
60 15 100       49 if ( $TRACKED{$address} ) {
61 2   100     12 $TRACKED{$address}->{class} ||= ''; # avoid warnings about uninitialised strings
62 2 50       8 if ( $class eq $TRACKED{$address}->{class} ) {
63             # Reblessing into the same class, ignore
64 0         0 return $OBJECT_COUNT{$class};
65             } else {
66             # Reblessing into a different class
67 2         7 $OBJECT_COUNT{$TRACKED{$address}->{class}}--;
68             }
69             }
70              
71             # Set or over-write the class name for the tracked object
72 15         54 my ($package, $srcfile, $srcline, $subroutine) = caller(1);
73 15   100     59 $package ||= '';
74 15   100     47 $subroutine ||= '';
75             #don't just tell us that we called it from our own new..
76 15 100       41 if ($package eq $class) {
77 3         13 my ($next_package, $next_srcfile, $next_srcline, $next_subroutine) = caller(2);
78 3 100       20 if ($next_subroutine eq $class.'::new') {
79 2         6 ($package, $srcfile, $srcline, $subroutine) = ($next_package, $next_srcfile, $next_srcline, $next_subroutine);
80             }
81             }
82 15         95 $TRACKED{$address} = { class => $class, file => $srcfile, line => $srcline, package=>$package, subroutine=>$subroutine };
83              
84             # If needed, initialise the new class
85 15 100       72 unless ( $DESTROY_STUBBED{$class} ) {
86 11 50 100     13 if ( exists ${$class.'::'}{DESTROY} and *{$class.'::DESTROY'}{CODE} ) {
  11         62  
  1         6  
87             # Stash the pre-existing DESTROY function
88 0         0 $DESTROY_ORIGINAL{$class} = \&{$class . '::DESTROY'};
  0         0  
89             }
90 11         23 $DESTROY_STUBBED{$class} = 1;
91 4 50 33 4   32 eval <<"END_DESTROY";
  4 50 33 4   8  
  4 50   1   1608  
  11 50       925  
  4 50       1838  
  4 100       11  
  4 100       13  
  0 50       0  
  0 100       0  
  4 50       11  
  0 50       0  
  4 50       532  
  4 50       10  
  3 100       9  
  0 50       0  
  3 50       4  
  3 100       10  
  0 0       0  
  3 0       10  
  3 0       8  
  0 0       0  
  0 0       0  
  1 0       1  
  4 0       12  
  1 0       3  
  4 0       8  
  4 50       13  
  0 50       0  
  0 50       0  
  4 50       11  
  2 50       2  
  2 50       10  
  2 50       11  
  3 50       10  
  0 50       0  
  0         0  
  1         2  
  4         12  
  0         0  
  4         8  
  4         14  
  0         0  
  0         0  
  4         9  
  1         2  
  1         5  
  3         18  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         6  
  1         5  
  1         8  
  0         0  
  0         0  
  1         3  
  0         0  
  1         5  
  1         4  
  1         4  
  0         0  
  1         3  
  1         5  
  0         0  
  1         5  
  1         4  
  0         0  
  0         0  
  0         0  
  1         5  
  0         0  
  1         3  
  1         5  
  0         0  
  0         0  
  1         3  
  0         0  
  0         0  
  1         24  
92             package $class;\
93             no warnings;
94             sub DESTROY {
95             my \$class = Scalar::Util::blessed(\$_[0]);
96             my \$address = Scalar::Util::refaddr(\$_[0]);
97             unless ( defined \$class ) {
98             Carp::carp("Unexpected error: First param to DESTROY is no an object");
99             return;
100             }
101             unless ( defined \$class ) {
102             die "Unexpected error: First param to DESTROY is no an object";
103             }
104              
105             # Don't do anything unless tracking for the specific object is set
106             my \$original = \$Devel::Leak::Object::TRACKED{\$address}->{class};
107             if ( \$original ) {
108             ### TODO - We COULD add a check that $class eq
109             # \$Devel::Leak::Object::TRACKED{\$address}->{class}
110             # and then not decrement unless it is the same.
111             # However, in practice it should ALWAYS be the same if
112             # we already have \$Devel::Leak::Object::TRACKED{\$address}
113             # true still, and if for some reason this is wrong, we get
114             # a false positive in the leak counting.
115             # This additional check may be able to be added at a later
116             # date if it turns out to be needed.
117             # if ( \$class eq \$Devel::Leak::Object::TRACKED{\$address} ) { ... }
118             if ( \$class ne \$original ) {
119             warn "Object class '\$class' does not match original ".\$Devel::Leak::Object::TRACKED{\$address}->{class};
120             }
121             \$Devel::Leak::Object::OBJECT_COUNT{\$original}--;
122             if ( \$Devel::Leak::Object::OBJECT_COUNT{\$original} < 0 ) {
123             warn "Object count for ".\$Devel::Leak::Object::TRACKED{\$address}->{class}." negative (\$Devel::Leak::Object::OBJECT_COUNT{\$original})";
124             }
125             delete \$Devel::Leak::Object::TRACKED{\$address};
126              
127             # Hand of to the regular DESTROY method, or pass up to the SUPERclass if there isn't one
128             if ( \$Devel::Leak::Object::DESTROY_ORIGINAL{\$original} ) {
129             goto \&{\$Devel::Leak::Object::DESTROY_ORIGINAL{\$original}};
130             }
131             } else {
132             \$original = \$class;
133             }
134              
135             # If we don't have the DESTROY_NEXT for this class, populate it
136             unless ( \$Devel::Leak::Object::DESTROY_NEXT{\$original} ) {
137             Devel::Leak::Object::make_next(\$original);
138             }
139             my \$super = \$Devel::Leak::Object::DESTROY_NEXT{\$original}->{'$class'};
140             unless (( defined \$super ) or (defined(\$Devel::Leak::Object::IGNORE_CLASS{\$class}))) {
141             warn "Failed to find super-method for class \$class in package $class";
142             \$Devel::Leak::Object::IGNORE_CLASS{\$class} = 1;
143             }
144             if ( \$super ) {
145             goto \&{\$super.'::DESTROY'};
146             }
147             return;
148             }
149             END_DESTROY
150 11 50       45 if ( $@ ) {
151 0         0 die "Failed to generate DESTROY method for $class: $@";
152             }
153              
154             # Pre-emptively populate the DESTROY_NEXT map
155 11 100       34 unless ( $DESTROY_NEXT{$class} ) {
156 10         30 make_next($class);
157             }
158             }
159              
160 15         53 $OBJECT_COUNT{$TRACKED{$address}->{class}}++;
161             }
162              
163             sub make_next {
164 14     14 0 1417 my $class = shift;
165              
166             # Build the %DESTROY_NEXT entries to support DESTROY_stub
167 14         37 $DESTROY_NEXT{$class} = {};
168 14         66 my @stack = ( $class );
169 11         32 my %seen = ( UNIVERSAL => 1 );
170 11         20 my @queue = ();
171 14         570 while ( my $c = shift @stack ) {
172 19 50       56 next if $seen{$c}++;
173            
174             # Does the class have it's own DESTROY method
175             my $has_destroy = $DESTROY_STUBBED{$c}
176             ? !! exists $DESTROY_ORIGINAL{$c}
177 22 100 66     62 : !! (exists ${"${c}::"}{DESTROY} and *{"${c}::DESTROY"}{CODE});
178 22 100       47 if ( $has_destroy ) {
179             # Everything in the queue has this class as it's next call
180 5         17 while ( @queue ) {
181 5         22 $DESTROY_NEXT{$class}->{shift(@queue)} = $c;
182             }
183             } else {
184             # This class goes onto the queue
185 20         34 push @queue, $c;
186             }
187              
188             # Add the @ISA to the search stack.
189 22         28 unshift @stack, @{"${c}::ISA"};
  19         123  
190             }
191              
192             # Any else has no target to go to
193 14         43 while ( @queue ) {
194 15         60 $DESTROY_NEXT{$class}->{shift @queue} = '';
195             }
196              
197 11         70 return 1;
198             }
199              
200             sub checkpoint {
201 0     4 0 0 my $first;
202 0         0 for (sort keys %OBJECT_COUNT) {
203 3 50       8 next unless $OBJECT_COUNT{$_}; # Don't list class with count zero
204 0   33     0 $OBJECT_COUNT_CHECKPOINT{$_} ||= 0;
205 3 50       4 next unless ($OBJECT_COUNT{$_} > $OBJECT_COUNT_CHECKPOINT{$_});
206            
207 3 50       8 print STDERR "checkpoint:\n" unless ($first++);;
208 0         0 printf STDERR "\t%-40s %d\n", $_, $OBJECT_COUNT{$_}-$OBJECT_COUNT_CHECKPOINT{$_};
209            
210 0         0 $OBJECT_COUNT_CHECKPOINT{$_} = $OBJECT_COUNT{$_};
211             }
212             }
213              
214             sub status {
215 7     4 0 225 print STDERR "Tracked objects by class:\n";
216 6         42 for (sort keys %OBJECT_COUNT) {
217 14 100       53 next if ($_ eq ''); #Don't know what these are..
218 12 100       44 next unless $OBJECT_COUNT{$_}; # Don't list class with count zero
219 7         1410 printf STDERR "\t%-40s %d\n", $_, $OBJECT_COUNT{$_};
220             }
221 8 50       48 if($Devel::Leak::Object::TRACKSOURCELINES) {
222 4         15 print STDERR "\nSources of leaks:\n";
223 0         0 my %classes = ();
224 0         0 foreach my $obj (values(%TRACKED)) {
225             #TODO: no, I don't know why there are some undefined
226 4 100       10 next unless defined($obj->{class});
227 0   0     0 $classes{$obj->{class}} ||= {};
228 4         12 my $line = $obj->{file}.' line: '.$obj->{line}; #.' ('.$obj->{package}.' -> '.$obj->{subroutine}.')';
229 4         11 $classes{$obj->{class}}->{$line}++;
230             }
231 3         9 foreach my $class (sort keys(%classes)) {
232 0         0 printf STDERR "%s\n", $class;
233 3         5 my %lines = %{$classes{$class}};
  3         19  
234 0         0 foreach my $line (sort keys(%lines)) {
235 3         11 printf STDERR "%6d from %s\n", $lines{$line}, $line;
236             }
237             }
238             }
239             }
240              
241             END {
242 4     4   3071 status();
243             }
244              
245             1;
246              
247             __END__