File Coverage

blib/lib/Devel/Calltree.pm
Criterion Covered Total %
statement 107 141 75.8
branch 23 50 46.0
condition 16 23 69.5
subroutine 22 26 84.6
pod 0 10 0.0
total 168 250 67.2


line stmt bran cond sub pod time code
1             package Devel::Calltree;
2              
3 2     2   17563 use 5.006;
  2         9  
  2         90  
4 2     2   10 use strict;
  2         4  
  2         78  
5              
6 2     2   11 use vars qw($VERSION);
  2         25  
  2         92  
7 2     2   2194 use B::Utils;
  2         14419  
  2         409  
8              
9             $VERSION = '0.00_3';
10              
11             our %OPT;
12             my $CURFILE;
13              
14             use overload '@{}' =>
15             sub {
16 2     2   48 my $calls = shift;
17             # the outer map() will turn __MAIN__ into 'z' x 100 in the hope
18             # that this will put '__MAIN__' at the end of the list
19 122 50       248 [ map $_->[0],
20 2 50       185 sort { $a->[1] cmp $b->[1] or $a->[2] cmp $b->[2] }
21             map [ $_, $_ ne '__MAIN__' ? /(.+)::(.+)/ : 'z' x 100 ], keys %$calls ];
22 2     2   6545 };
  2         2390  
  2         31  
23              
24             sub import {
25 2     2   80 my $class = shift;
26 2         11 %OPT = @_;
27 2         4 push @{ $OPT{-exclude} }, __PACKAGE__;
  2         7  
28 2 50       15 if (my $h = $OPT{-output}) {
29 2 50       16 if (ref($h) eq 'GLOB') {
30 0         0 select $h;
31             } else {
32 2 50       276 open FH, ">$h" or die "Could not open $h for output: $!";
33 2         10 select FH;
34             }
35             }
36             # when -test is set, we are running the tests.
37             # in order to get consistent output, $CURFILE will
38             # be tied so that it always return 'XXX'.
39 2 50       19 tie $CURFILE => "Devel::Calltree::testmode" if $OPT{-test};
40 2         9 setup_reportfuncs($OPT{ -reportfuncs });
41             }
42              
43             sub INIT {
44              
45 2     2   13 my %root = B::Utils::all_roots();
46 2         162178 remove_excluded(\%root);
47 2         30 my @pkgs = get_packages(\%root);
48            
49 2         4 my %CALLS;
50 2         25 while (my ($name, $root) = each %root) {
51 40         233 my ($pkg) = $name =~ /(.*)::/;
52 40         69 my @CALLS;
53            
54             # we pass the current package name ($pkg)
55             # so that we can find the real
56             # package of $pkg::func in find_subcall().
57 40         163 B::Utils::walkoptree_simple($root, \&find_subcall, [\@CALLS, $pkg]);
58 40         1742 $CALLS{ $name } = bless [] => $CURFILE;
59 40         211 for my $call (@CALLS) {
60 74         66 push @{ $CALLS{$name} }, $call;
  74         157  
61             }
62             }
63 2         8 my $calls = bless \%CALLS => __PACKAGE__;
64 2 50       14 if (!$OPT{-iscalled}) {
65 2         9 $calls->print_report;
66             } else {
67 0         0 $calls->print_report_called;
68             }
69 2         344 exit;
70             }
71              
72             sub find_subcall {
73 2458     2458 0 67483 my ($op, $args) = @_;
74            
75 2458         3384 my ($data, $name) = @$args;
76            
77 2458         4924 $CURFILE = $B::Utils::file;
78              
79             # function call
80 2458 100 66     10767 if ($op->name eq 'gv' && $op->next && $op->next->name eq 'entersub') {
      100        
81 10         12 my $realfnc;
82 10         126 my $fnc = join '::', $op->gv->STASH->NAME, $op->gv->NAME;
83            
84             # do we need to attempt resolving the real function name?
85             # this assumes that a call to a fully qualified function
86             # can be taken as is (unless it is main::func()).
87 10 100 66     103 if ($op->gv->STASH->NAME eq $name or $op->gv->STASH->NAME eq 'main') {
88 2     2   1176 no strict 'refs';
  2         4  
  2         3050  
89             # not sure why this happens:
90             # sometimes B::svref_2object(...)->STASH returns a B::SPECIAL
91             # which has no NAME() method
92 8         40 my $pkg = eval { B::svref_2object(\&$fnc)->STASH->NAME };
  8         69  
93 8   50     27 $pkg ||= '??';
94 8         43 $fnc =~ s/.*:://;
95 8         28 $realfnc = "${pkg}::$fnc";
96             } else {
97 2         5 $realfnc = $fnc;
98             }
99 10         24 $B::Utils::file =~ tr#/##s; # squeeze: blib/lib//bla.pm => blib/lib/bla.pm
100 10         60 push @$data, bless { name => $realfnc,
101             line => $B::Utils::line,
102             file => $B::Utils::file,
103             is_method => 0 } => 'Devel::Calltree::Func';
104 10         29 return;
105             }
106            
107             # method call
108 2448 50 66     13197 if ($op->name eq 'method_named' && $op->next && $op->next->name eq 'entersub') {
      66        
109 64         592 push @$data, bless { name => $op->gv->PV,
110             line => $B::Utils::line,
111             file => $B::Utils::file,
112             is_method => 1 } => 'Devel::Calltree::Func';
113             }
114              
115             }
116              
117             sub print_report {
118 1     1 0 2 my $calls = shift;
119 1         37 for my $caller (@$calls) {
120 20         38 my $file = file($calls->{ $caller });
121 20 50 66     35 if (funcs($calls->{$caller}) || !$OPT{-filter_empty} ) {
122 20         45 print "\n$caller ($file): \n";
123 20         33 for my $targ (funcs($calls->{$caller})) {
124 37         62 my $n = $targ->name;
125 37         60 my $l = $targ->line;
126 37 100       75 if ($targ->is_method) {
127 32         97 print " method '$n'", ' ' x (60 - 14 - length($n)), " ($l)\n";
128 32         53 next;
129             }
130 5         14 print " function '$n'", ' ' x (60 - 14 - length($n)), " ($l)\n";
131             }
132             }
133             }
134             }
135              
136             sub print_report_called {
137            
138 0     0 0 0 my $calls = shift;
139 0         0 my @funcs = @{ $OPT{ -iscalled } };
  0         0  
140 0         0 my %notfound;
141 0         0 @notfound{ @funcs } = ();
142 0         0 my $pat = "(" . $funcs[0] . ")";
143 0         0 $pat .= "|($_)" for @funcs[1..$#funcs];
144 0         0 my @found;
145            
146 0         0 while (my ($caller, $candid) = each %$calls) {
147 0         0 for (@$candid) {
148 0 0       0 if ($_->name =~ /$pat/o) {
149 0         0 delete $notfound{ $funcs[$+] };
150 0         0 push @found, [ $caller, $_ ];
151             }
152             }
153             }
154 0 0       0 if (keys %notfound) {
155 0         0 print "These patterns did not match any called function:\n";
156 0         0 print " $_\n" for keys %notfound;
157 0         0 print "\n";
158             }
159              
160 0         0 print "These functions were called:\n";
161 0         0 for (sort { $a->[1]->name cmp $b->[1]->name } @found) {
  0         0  
162 0 0       0 printf " %-8s %-30s from %-30s at line %i\n", $_->[1]->is_method ? "method" : "function",
163             $_->[1]->name, $_->[0], $_->[1]->line;
164             }
165             }
166            
167            
168             sub remove_excluded {
169 2     2 0 6 my $roots = shift;
170 2         6 my @bad;
171 2         6 my $patbad = join "|^", @{ $OPT{-exclude} };
  2         16  
172             # no include pattern: come up with one that always fails
173 2 50       6 my $patgood = join "|^", @{ $OPT{-include} || [ qw/&!%"§@@/ ]};
  2         15  
174 2         46 $patbad = qr/^$patbad/;
175 2         28 $patgood = qr/^$patgood/;
176            
177 2         200 for (keys %$roots) {
178 2250 100 66     10755 push @bad, $_ if /$patbad/o && !/$patgood/o;
179             }
180 2         571 delete @$roots{ @bad };
181             }
182            
183             sub get_packages {
184 2     2 0 7 my $roots = shift;
185 2 50       27 my %pkgs = map { /(.+)::/ ? $1 : __MAIN__ => 1 } keys %$roots;
  40         192  
186 2         21 keys %pkgs;
187             }
188            
189             sub array_to_hash {
190 0     0 0 0 my @array = @_;
191 0         0 my %hash;
192 0         0 @hash{ @array } = (1) x @array;
193 0         0 return %hash;
194             }
195              
196             sub sort {
197 0     0 0 0 my $self = shift;
198 0 0       0 $self->{ "sorted\0" } = [ # prevent clash with function name
199             map $_->[0],
200 0 0       0 sort { $a->[1] cmp $b->[1] or $a->[2] cmp $b->[2] }
201             map [ $_, $_ ne '__MAIN__' ? /(.+)::(.+)/ : 'z' x 100 ], keys %$self ];
202             }
203            
204             sub setup_reportfuncs {
205 2     2 0 5 my $file = shift;
206 2 100       2199 return if ! defined $file;
207 1         6 local $^W = 0;
208 1 50       832 unless (my $err = do $file) {
209 0 0       0 die "Can't parse '$file':\n$@\n" if $@;
210 0 0       0 die "Can't open '$file':\n$!\n" if !defined $err;
211 0         0 die "Can't run '$file':\nMaybe it didn't return a true value?\n";
212             }
213             }
214              
215 0     0   0 sub Devel::Calltree::Func::file { shift->{ file } }
216 74     74   171 sub Devel::Calltree::Func::line { shift->{ line } }
217 74     74   334 sub Devel::Calltree::Func::name { shift->{ name } }
218 74     74   191 sub Devel::Calltree::Func::is_method { shift->{ is_method } }
219              
220              
221 2     2   9 sub Devel::Calltree::testmode::TIESCALAR { bless \my $var => "Devel::Calltree::testmode" }
222 40     40   163 sub Devel::Calltree::testmode::FETCH { "XXX" }
223 2458     2458   3507 sub Devel::Calltree::testmode::STORE { }
224              
225             # functions to be used by the --reportfuncs snipplets
226              
227 40     40 0 158 sub file { my $f = ref $_[0]; $f =~ tr#/##s; $f }
  40         52  
  40         77  
228 80 50   80 0 214 sub funcs { @{ $_[0] || [] } }
  80         346  
229              
230             1;
231             __END__