File Coverage

lib/Devel/Leak/Cb.pm
Criterion Covered Total %
statement 70 75 93.3
branch 13 14 92.8
condition 3 6 50.0
subroutine 15 15 100.0
pod 0 3 0.0
total 101 113 89.3


line stmt bran cond sub pod time code
1             package Devel::Leak::Cb;
2              
3 9     9   272741 use 5.008008;
  9         33  
  9         359  
4 9     9   52 use common::sense;
  9         18  
  9         70  
5             m{
6             use strict;
7             use warnings;
8             }x;
9             =head1 NAME
10              
11             Devel::Leak::Cb - Detect leaked callbacks
12              
13             =head1 VERSION
14              
15             Version 0.04
16              
17             =cut
18              
19             our $VERSION = '0.04';
20              
21             =head1 SYNOPSIS
22              
23             use Devel::Leak::Cb;
24            
25             AnyEvent->timer( after => 1, cb => cb {
26             ...
27             });
28            
29             # If $ENV{DEBUG_CB} is true and callback not destroyed till END, the you'll be noticed
30              
31             =head1 DESCRIPTION
32              
33             By default, cb { .. } will be rewritten as sub { .. } using L and will give no additional cost at runtime
34              
35             When C<$ENV{DEBUG_CB}> will be set, then all cb {} declarations will be counted, and if some of them will not be destroyed till the END stage, you'll be warned
36              
37             =head1 EXPORT
38              
39             Exports a single function: cb {}, which would be rewritten as sub {} when C<$ENV{DEBUG_CB}> is not in effect
40              
41             If C > 1 and L is installed, then output will include reference tree of leaked callbacks
42              
43             =head1 FUNCTIONS
44              
45             =head2 cb {}
46              
47             Create anonymous callback
48              
49             my $cb = cb {};
50              
51             =head2 cb name {}
52              
53             Create named callback with static name (Have no effect without C<$ENV{DEBUG_CB}>)
54              
55             my $cb = cb mycallback {};
56              
57             =head2 cb $name {}
58              
59             Create named callback with dynamic name (Have no effect without C<$ENV{DEBUG_CB}>)
60             $name could me only simple scalar identifier, without any special symbols
61              
62             my $cb = cb $name {};
63             my $cb = cb $full::name {};
64              
65             =head2 cb 'name' {}
66              
67             Create named callback with dynamic name (Have no effect without C<$ENV{DEBUG_CB}>)
68             Currently supported only ' and ". Quote-like operators support will be later
69              
70             my $cb = cb 'name' {};
71             my $cb = cb "name.$val" {};
72              
73             =head2 COUNT
74              
75             You may call C Manually to check state. All leaked callbacks will be warned. Noop without C<$ENV{DEBUG_CB}>
76              
77             =cut
78              
79 9     9   9354 use Devel::Declare ();
  9         86924  
  9         269  
80 9     9   83 use Scalar::Util 'weaken';
  9         20  
  9         2195  
81              
82             our @CARP_NOT = qw(Devel::Declare);
83             our %DEF;
84              
85             BEGIN {
86 9 100   9   56 if ($ENV{DEBUG_CB}) {
87 4         12 my $debug = $ENV{DEBUG_CB};
88 4         2628 *DEBUG = sub () { $debug };
  0         0  
89             } else {
90 5         3175 *DEBUG = sub () { 0 };
91             }
92             }
93              
94              
95             BEGIN {
96 9     9   21 if (DEBUG){
97             eval { require Sub::Identify; Sub::Identify->import('sub_fullname'); 1 } or *sub_fullname = sub { return };
98             eval { require Sub::Name; Sub::Name->import('subname'); 1 } or *subname = sub { $_[1] };
99             eval { require Devel::Refcount; Devel::Refcount->import('refcount'); 1 } or *refcount = sub { -1 };
100             *COUNT = sub () {
101       4     for (keys %DEF) {
102             my $d = delete $DEF{$_};
103             #print STDERR "Counting $_ [ @$d ]";
104             $d->[0] or next;
105             my $name = $d->[4] ? $d->[1].'::cb.'.$d->[4] : sub_fullname($d->[0]) || $d->[1].'::cb.__ANON__';
106             substr($name,-10) eq '::__ANON__' and substr($name,-10) = '::cb.__ANON__';
107             warn "Leaked: $name (refs:".refcount($d->[0]).") defined at $d->[2] line $d->[3]\n".(DEBUG > 1 ? findref($d->[0]) : '' );
108             }
109             };
110             } else {
111 9     5   32 *COUNT = sub () {};
  5         32  
112             }
113 9         4087 if (DEBUG>1) {
114             eval { require Devel::FindRef; *findref = \&Devel::FindRef::track; 1 } or *findref = sub { "No Devel::FindRef installed\n" };
115             }
116             }
117              
118             sub import{
119 13     9   3642 my $class = shift;
120 13         2926 my $caller = caller;
121 13         113 Devel::Declare->setup_for(
122             $caller,
123             { 'cb' => { const => \&parse } }
124             );
125             {
126 13     9   4495 no strict 'refs';
  13         38  
  13         10751  
  13         269  
127 9         19 *{$caller.'::cb' } = sub() { 1 };
  13         6967  
128             }
129             }
130              
131             sub __cb__::DESTROY {
132             #print STDERR "destroy $_[0]\n";
133 6     2   5349 delete($DEF{int $_[0]});
134             };
135              
136             our $LASTNAME;
137              
138             sub remebmer($) {
139 13     9 0 3242 $LASTNAME = $_[0];
140 13         74 return 1;
141             }
142              
143             sub wrapper (&) {
144 13     9 0 222 $DEF{int $_[0]} = [ $_[0], (caller)[0..2], $LASTNAME ];
145 16         5065 weaken($DEF{int $_[0]}[0]);
146 16 100       120 subname($DEF{int $_[0]}[1].'::cb.'.$LASTNAME => $_[0]) if $LASTNAME;
147 16         58 $LASTNAME = undef;
148 16         121 return bless $_[0],'__cb__';
149             }
150              
151             sub parse {
152 28     21 0 4007 my $offset = $_[1];
153 25         460 $offset += Devel::Declare::toke_move_past_token($offset);
154 21         54 $offset += Devel::Declare::toke_skipspace($offset);
155 21         31 my $name = 'undef';
156 21         56 my $line = Devel::Declare::get_linestr();
157            
158 21 100 66     260 if (
    100          
    100          
159             substr($line,$offset,1) =~ /^('|")/ # '
160             and my $len = Devel::Declare::toke_scan_str($offset)
161             ){
162 2         8 my $lex = $1;
163 2         7 my $st = Devel::Declare::get_lex_stuff();
164 2         4 Devel::Declare::clear_lex_stuff();
165             #warn "Got lex $lex >$st<";
166 2         6 my $linestr = Devel::Declare::get_linestr();
167 2 50 33     17 if ( $len < 0 or $offset + $len > length($linestr) ) {
168 0         0 require Carp;
169 0         0 Carp::croak("Unbalanced text supplied");
170             }
171 2         5 substr($linestr, $offset, $len) = '';
172 2         6 Devel::Declare::set_linestr($linestr);
173 2         5 $name = qq{$lex$st$lex};
174            
175             }
176             elsif (my $len = Devel::Declare::toke_scan_word($offset, 1)) {
177 5         21 my $linestr = Devel::Declare::get_linestr();
178 5         12 $name = substr($linestr, $offset, $len);
179 5         13 substr($linestr, $offset, $len) = '';
180 5         45 Devel::Declare::set_linestr($linestr);
181 5         11 $offset += Devel::Declare::toke_skipspace($offset);
182 5         22 $name = qq{'$name'};
183             }
184             elsif (substr(my $line = Devel::Declare::get_linestr(),$offset,1) eq '$') {
185 5 100       16 if (my $len = Devel::Declare::toke_scan_word($offset+1, 1)) {
186 5         12 my $linestr = Devel::Declare::get_linestr();
187 5         10 $name = substr($linestr, $offset, $len+1);
188 5         9 substr($linestr, $offset, $len+1) = '';
189 5         8 Devel::Declare::set_linestr($linestr);
190 5         11 $offset += Devel::Declare::toke_skipspace($offset);
191 5         8 $name = qq{$name};
192             } else {
193 0         0 die("Bad syntax: $line at @{[ (caller 1)[1] ]}");
  0         0  
194             }
195             }
196            
197 21         57 my $linestr = Devel::Declare::get_linestr();
198 21         28 if (DEBUG) {
199             substr($linestr,$offset,0) = '&& Devel::Leak::Cb::remebmer('.$name.') && Devel::Leak::Cb::wrapper ';
200             Devel::Declare::set_linestr($linestr);
201             } else {
202 21         42 substr($linestr,$offset,0) = '&& sub ';
203 21         48 Devel::Declare::set_linestr($linestr);
204             }
205             #warn $linestr;
206 21         137 return;
207             }
208              
209             END {
210 9     9   12379 COUNT();
211             }
212              
213             =head1 AUTHOR
214              
215             Mons Anderson, C<< >>
216              
217             =head1 BUGS
218              
219             Please report any bugs or feature requests to C, or through
220             the web interface at L. I will be notified, and then you'll
221             automatically be notified of progress on your bug as I make changes.
222              
223             =head1 SUPPORT
224              
225             You can find documentation for this module with the perldoc command.
226              
227             perldoc Devel::Leak::Cb
228              
229             You can also look for information at:
230              
231             =over 4
232              
233             =item * RT: CPAN's request tracker
234              
235             L
236              
237             =back
238              
239             =head1 COPYRIGHT & LICENSE
240              
241             Copyright 2009 Mons Anderson, all rights reserved.
242              
243             This program is free software; you can redistribute it and/or modify it
244             under the same terms as Perl itself.
245              
246             =cut
247              
248             1; # End of Devel::Leak::Cb