File Coverage

blib/lib/Devel/LeakGuard/Object/State.pm
Criterion Covered Total %
statement 99 101 98.0
branch 43 52 82.6
condition 9 9 100.0
subroutine 18 18 100.0
pod 2 2 100.0
total 171 182 93.9


line stmt bran cond sub pod time code
1             package Devel::LeakGuard::Object::State;
2              
3 6     6   97546 use 5.008;
  6         22  
4              
5 6     6   32 use strict;
  6         11  
  6         151  
6 6     6   38 use warnings;
  6         12  
  6         264  
7              
8 6     6   48 use Carp qw( croak carp );
  6         10  
  6         401  
9 6     6   1114 use Devel::LeakGuard::Object;
  6         13  
  6         173  
10 6     6   34 use List::Util qw( max );
  6         9  
  6         8720  
11              
12             =head1 NAME
13              
14             Devel::LeakGuard::Object::State - Scoped object leak checking
15              
16             =head1 VERSION
17              
18             This document describes Devel::LeakGuard::Object::State version 0.07
19              
20             =cut
21              
22             our $VERSION = '0.07';
23              
24             =head1 SYNOPSIS
25              
26             use Devel::LeakGuard::Object::State;
27              
28             # Later
29             my $leakstate = Devel::LeakGuard::Object::State->new(
30             on_leak => 'die'
31             );
32              
33             My::Thing->leaky();
34              
35             $leakstate->done;
36              
37             =head1 DESCRIPTION
38              
39             A C captures the current leakstate of
40             object allocations within a program. When L is called the saved
41             allocation leakstate is compared with the current leakstate and any
42             discrepancies are reported.
43              
44             =head1 INTERFACE
45              
46             =head2 C<< new >>
47              
48             Create a new C. A number of options may
49             be supplied. To see the full list refer to
50             L.
51              
52             =cut
53              
54             sub new {
55 22     22 1 53 my $class = shift;
56 22         73 my ( $pkg, $file, $line ) = caller;
57 22 50       61 croak "expected a number of key => value options" if @_ % 1;
58              
59 22         59 my %opt = @_;
60 22   100     73 my $on_leak = delete $opt{on_leak} || 'warn';
61              
62 22 100       62 return bless {}, 'Devel::LeakGuard::Object::State::Nop'
63             if $on_leak eq 'ignore';
64              
65 21         59 Devel::LeakGuard::Object::_adj_magic( 1 );
66              
67 21         57 my $self
68             = bless { leakstate => Devel::LeakGuard::Object::leakstate() },
69             $class;
70              
71             $self->{on_leak} = $on_leak eq 'die'
72             ? sub {
73 1     1   9 $class->_with_report( shift, sub { croak @_ } );
  1         182  
74             }
75             : $on_leak eq 'warn' ? sub {
76 2     2   47 $class->_with_report( shift, sub { carp @_ } );
  2         386  
77             }
78 21 100       118 : $on_leak;
    100          
79              
80             croak "on_leak must be a coderef, 'warn' or 'die'"
81 21 50       54 unless 'CODE' eq ref $self->{on_leak};
82              
83 21         109 $self->{$_} = delete $opt{$_} for qw( expect only exclude );
84              
85 21 50       53 croak "invalid option(s): ", sort keys %opt if keys %opt;
86              
87             # print "new $class at $file, $line\n";
88              
89 21         68 return $self;
90             }
91              
92       1     sub Devel::LeakGuard::Object::State::Nop::done { }
93              
94             sub _with_report {
95 3     3   7 my ( $class, $rep, $cb ) = @_;
96              
97 3         19 local %Carp::Internal = (
98             %Carp::Internal,
99             'Devel::LeakGuard::Object' => 1,
100             'Devel::LeakGuard::Object::State' => 1,
101             $class => 1
102             );
103              
104 3         8 $cb->(
105             "Object leaks found:\n",
106             $class->_fmt_report( $rep ), "\nDetected"
107             );
108             }
109              
110             sub _fmt_report {
111 3     3   5 my ( $class, $rep ) = @_;
112 3         7 my $l = max( 5, map { length $_ } keys %$rep );
  3         11  
113 3         9 my $fmt = " %-${l}s %6s %6s %6s";
114 3         15 my @r = sprintf $fmt, 'Class', 'Before', 'After', 'Delta';
115 3         8 for my $cl ( sort keys %$rep ) {
116 3         17 push @r, sprintf $fmt, $cl, @{ $rep->{$cl} },
117 3         5 $rep->{$cl}[1] - $rep->{$cl}[0];
118             }
119 3         13 return join "\n", @r;
120             }
121              
122             sub _make_matcher {
123 6     6   9 my ( $self, $filter ) = @_;
124 6         7 my @m = ();
125 6 100       15 for my $elt ( 'ARRAY' eq ref $filter ? @$filter : $filter ) {
126 7 100       15 unless ( ref $elt ) {
127             my $pat = join '',
128 6 100       15 map { '*' eq $_ ? '.*?' : quotemeta $_ } split //, $elt;
  21         67  
129 6         61 $elt = qr{^$pat$};
130             }
131              
132 7 50       15 if ( 'Regexp' eq ref $elt ) {
    0          
133 7     24   25 push @m, sub { $_ =~ $elt };
  24         128  
134             }
135             elsif ( 'CODE' eq ref $elt ) {
136 0         0 push @m, $elt;
137             }
138             else {
139 0         0 croak "Bad filter spec";
140             }
141             }
142              
143             return sub {
144 22     22   27 local $_ = shift;
145 22         26 for my $m ( @m ) {
146 24 100       43 return 1 if $m->();
147             }
148 12         40 return;
149 6         16 };
150             }
151              
152             sub _filter {
153 6     6   12 my ( $self, $filter, $invert, @list ) = @_;
154 6         11 my $m = $self->_make_matcher( $filter );
155             return $invert
156 6         9 ? grep { !$m->( $_ ) } @list
157 6 100       17 : grep { $m->( $_ ) } @list;
  16         26  
158             }
159              
160             =head2 C<< done >>
161              
162             Call C at the end of the area of code to be leak-checked. If
163             allocation imbalances are detected the action taken depends on the
164             options passed to L. By default a warning is displayed.
165              
166             =cut
167              
168             sub done {
169 40     40 1 50 my $self = shift;
170 40         46 local $@;
171             # my ( $pkg, $file, $line ) = caller;
172             # print "done ", ref $self, " at $file, $line\n";
173 40 100       175 return if $self->{done}++;
174              
175 21         50 Devel::LeakGuard::Object::_adj_magic( -1 );
176 21         56 my $leakstate = Devel::LeakGuard::Object::leakstate();
177 21         43 my %seen = ();
178 21         28 my %report = ();
179              
180 21         26 for my $class ( sort keys %{ $self->{leakstate} }, %$leakstate ) {
  21         147  
181 157 100       412 next if $seen{$class}++;
182 97   100     261 my $before = $self->{leakstate}{$class} || 0;
183 97   100     239 my $after = $leakstate->{$class} || 0;
184 97 100       261 $report{$class} = [ $before, $after ] if $before != $after;
185             }
186              
187 21         67 my @keep = keys %report;
188 21 100       60 return unless @keep;
189              
190             @keep = $self->_filter( $self->{only}, 0, @keep )
191 18 100       55 if defined $self->{only};
192 18 50       52 return unless @keep;
193              
194             @keep = $self->_filter( $self->{exclude}, 1, @keep )
195 18 100       44 if defined $self->{exclude};
196 18 50       34 return unless @keep;
197              
198 18 100       50 if ( my $exp = $self->{expect} ) {
199 6         7 my @k = ();
200 6         9 PKG: for my $pkg ( @keep ) {
201 24 100       46 if ( defined( my $range = $exp->{$pkg} ) ) {
202 5 100       14 $range = [ $range, $range ] unless 'ARRAY' eq ref $range;
203 5         8 my $delta = $report{$pkg}[1] - $report{$pkg}[0];
204 5 100 100     27 next PKG if $delta >= $range->[0] && $delta <= $range->[1];
205             }
206 21         30 push @k, $pkg;
207             }
208 6         16 @keep = @k;
209             }
210 18 50       30 return unless @keep;
211 18         28 my %filtrep = ();
212 18         67 $filtrep{$_} = $report{$_} for @keep;
213 18         55 $self->{on_leak}( \%filtrep );
214             }
215              
216 21     21   118 sub DESTROY { shift->done }
217              
218             1;
219              
220             =head1 AUTHOR
221              
222             Andy Armstrong C<< >>
223              
224             =head1 LICENCE AND COPYRIGHT
225              
226             Copyright (c) 2009-2015, Andy Armstrong C<< >>.
227              
228             This module is free software; you can redistribute it and/or
229             modify it under the same terms as Perl itself. See L.
230              
231             =cut