File Coverage

blib/lib/Devel/TraceUse.pm
Criterion Covered Total %
statement 65 105 61.9
branch 25 74 33.7
condition 3 7 42.8
subroutine 11 13 84.6
pod 0 6 0.0
total 104 205 50.7


line stmt bran cond sub pod time code
1             package Devel::TraceUse;
2             $Devel::TraceUse::VERSION = '2.095';
3             # detect being loaded via -d:TraceUse and disable the debugger features we
4             # don't need. better names for evals (0x100) and anon subs (0x200).
5             BEGIN {
6 3 50 33 3   149753 if (!defined &DB::DB && $^P & 0x02) {
7 0         0 $^P = 0x100 | 0x200;
8             }
9             }
10              
11             BEGIN
12             {
13 3 50   3   8 unshift @INC, \&trace_use unless grep { "$_" eq \&trace_use . '' } @INC;
  32         5362  
14             }
15              
16             # initialize the tree of require calls
17             my $root = (caller)[1];
18             my %used; # track loaded modules by "filename" (parameter to require)
19             my %loaded; # track "filename"s loaded by "filepath" (value from %INC)
20             my %reported; # track reported "filename"
21             my %loader; # track potential proxy modules
22             my $rank = 0; # record the loading order of modules
23             my $quiet = 1; # no output until decided otherwise
24             my $output_fh; # optional write filehandle where results will be output
25              
26             # Hide core modules (for the specified version)?
27             my $hide_core = 0;
28              
29             sub import {
30 2     2   1298 my $class = shift;
31              
32             # ensure "use Devel::TraceUse ();" will produce no output
33 2         2 $quiet = 0;
34              
35             # process options
36 2         18 for(@_) {
37 0 0       0 if(/^hidecore(?::(.*))?/) {
    0          
38 0 0       0 $hide_core = numify( $1 ? $1 : $] );
39             } elsif (/^output:(.*)$/) {
40 0 0       0 open $output_fh, '>', $1 or die "can't open $1: $!";
41             } else {
42 0         0 die "Unknown argument to $class: $_\n";
43             }
44             }
45             }
46              
47             my @caller_info = qw( package filepath line );
48              
49             # Keys used in the data structure:
50             # - filename: parameter given to use/require
51             # - module: module, computed from filename
52             # - rank: rank of loading
53             # - eval: was this use/require done in an eval?
54             # - loaded: list of files loaded from this one
55             # - filepath: file that was actually loaded from disk (obtained from %INC)
56             # - caller: information on the caller (same keys + everything from caller())
57              
58             sub trace_use
59             {
60 17     17 0 142591 my ( $code, $filename ) = @_;
61              
62             # ensure our hook remains first in @INC
63 17 50       65 @INC = ( $code, grep { $_ ne $code } @INC )
  0         0  
64             if $INC[0] ne $code;
65              
66             # $filename may be an actual filename, e.g. with do()
67             # try to compute a module name from it
68 17         21 my $module = $filename;
69 17 50       151 $module =~ s{/}{::}g
70             if $module =~ s/\.pm$//;
71              
72             # info about the module being loaded
73 17         20 push @{ $used{$filename} }, my $info = {
  17         108  
74             filename => $filename,
75             module => $module,
76             rank => ++$rank,
77             eval => '',
78             };
79              
80             # info about the loading module
81 17         35 my $caller = $info->{caller} = {};
82 17         40 @{$caller}{@caller_info} = caller;
  17         54  
83              
84             # try to compute a "filename" (as received by require)
85 17         28 $caller->{filename} = $caller->{filepath};
86              
87             # some values seen in the wild:
88             # - "(eval $num)[$path:$line]" (debugger)
89             # - "$filename (autosplit into $path)" (AutoLoader)
90 17 100       57 if ( $caller->{filename} =~ /^(\(eval \d+\))(?:\[(.*):(\d+)\])?$/ ) {
91 4         10 $info->{eval} = $1;
92 4         11 $caller->{filename} = $caller->{filepath} = $2;
93 4         8 $caller->{line} = $3;
94             }
95              
96             # clean up path
97             $caller->{filename}
98 17         17 =~ s!^(?:@{[ join '|', map quotemeta, reverse sort @INC ]})/?!!;
  17         785  
99              
100             # try to compute the package associated with the file
101 17         59 $caller->{filepackage} = $caller->{filename};
102 17         31 $caller->{filepackage} =~ s/\.(pm|al)\s.*$/.$1/;
103             $caller->{filepackage} =~ s{/}{::}g
104 17 100       113 if $caller->{filepackage} =~ s/\.pm$//;
105              
106             # record who tried to load us
107 17         20 push @{ $loaded{ $caller->{filepath} } }, $info->{filename};
  17         50  
108              
109             # record potential proxies
110 17 50       42 if ( $caller->{filename} ) {
111 17         15 my($subroutine, $level);
112 17   100     114 while ( $subroutine = ( caller ++$level )[3] || '' ) {
113 20 100       94 last if $subroutine =~ /::/;
114             }
115 17         19 $loader{ join "\0", @{$caller}{qw( filename line )}, $subroutine }++;
  17         62  
116             }
117              
118             # let Perl ultimately find the required file
119 17         11763 return;
120             }
121              
122             sub show_trace_visitor
123             {
124 0     0 0 0 my ( $mod, $pos, $output_cb, @args ) = @_;
125              
126 0         0 my $caller = $mod->{caller};
127 0         0 my $message = sprintf( '%4s.', $mod->{rank} ) . ' ' x $pos;
128 0         0 $message .= "$mod->{module}";
129 0         0 my $version = ${"$mod->{module}\::VERSION"};
  0         0  
130 0 0       0 $message .= defined $version ? " $version," : ',';
131             $message .= " $caller->{filename}"
132 0 0       0 if defined $caller->{filename};
133             $message .= " line $caller->{line}"
134 0 0       0 if defined $caller->{line};
135             $message .= " $mod->{eval}"
136 0 0       0 if $mod->{eval};
137             $message .= " [$caller->{package}]"
138 0 0       0 if $caller->{package} ne $caller->{filepackage};
139             $message .= " (FAILED)"
140 0 0       0 if !exists $INC{$mod->{filename}};
141              
142 0         0 $output_cb->($message, @args);
143             }
144              
145             sub visit_trace
146             {
147 1     1 0 2 my ( $visitor, $mod, $pos, @args ) = @_;
148              
149 1         1 my $hide = 0;
150              
151 1 50       11 if ( ref $mod ) {
152 0         0 $mod = shift @$mod;
153              
154 0 0       0 if($hide_core) {
155 0         0 $hide = exists $Module::CoreList::version{$hide_core}{$mod->{module}};
156             }
157              
158 0 0       0 $visitor->( $mod, $pos, @args ) unless $hide;
159              
160 0         0 $reported{$mod->{filename}}++;
161             }
162             else {
163 1         3 $mod = { loaded => delete $loaded{$mod} };
164             }
165              
166             visit_trace( $visitor, $used{$_}, $hide ? $pos : $pos + 1, @args )
167 1 0       1 for map { $INC{$_} || $_ } @{ $mod->{loaded} };
  0 0       0  
  1         5  
168             }
169              
170             # we don't want to use version.pm on old Perls
171             sub numify {
172 24     24 0 46615 my ($version) = @_;
173 24         42 $version =~ y/_//d;
174 24         79 my @parts = split /\./, $version;
175              
176             # %Module::CoreList::version's keys are x.yyyzzz *numbers*
177 24         200 return 0+ join '', shift @parts, '.', map sprintf( '%03s', $_ ), @parts;
178             }
179              
180             sub dump_proxies
181             {
182 1     1 0 2 my $output = shift;
183              
184             my @hot_loaders =
185 0         0 sort { $loader{$b} <=> $loader{$a} }
186 1         3 grep { $loader{$_} > 1 }
  0         0  
187             keys %loader;
188              
189 1 50       4 return unless @hot_loaders;
190              
191 0         0 $output->("Possible proxies:");
192              
193 0         0 for my $loader (@hot_loaders) {
194 0         0 my ( $filename, $line, $subroutine ) = split /\0/, $loader;
195             $output->(sprintf("%4d %s line %d%s",
196 0 0       0 $loader{$loader},
197             $filename, $line,
198             (defined($subroutine) ? ", sub $subroutine" : '')));
199             }
200             }
201              
202             sub dump_result
203             {
204 3 100   3 0 20 return if $quiet;
205              
206             # map "filename" to "filepath" for everything that was loaded
207 1         7 while ( my ( $filename, $filepath ) = each %INC ) {
208 74 50       275 if ( exists $used{$filename} ) {
209 0   0     0 $used{$filename}[0]{loaded} = delete $loaded{$filepath} || [];
210 0         0 $used{$filepath} = delete $used{$filename};
211             }
212             }
213              
214             # let people know more accurate information is available
215 1 50       6 warn "Use -d:TraceUse for more accurate information.\n" if !$^P;
216              
217             # load Module::CoreList if needed
218 1 50       3 if ($hide_core) {
219 0         0 local @INC = grep { $_ ne \&trace_use } @INC;
  0         0  
220 0         0 local %INC = %INC; # don't report it loaded
221 0         0 require Module::CoreList;
222             warn sprintf "Module::CoreList %s doesn't know about Perl %s\n",
223             $Module::CoreList::VERSION, $hide_core
224 0 0       0 if !exists $Module::CoreList::version{$hide_core};
225             }
226              
227             my $output = defined $output_fh
228 0     0   0 ? sub { print $output_fh "$_[0]\n" }
229 1 50   75   7 : sub { warn "$_[0]\n" };
  75         495  
230              
231             # output the diagnostic
232 1         4 $output->("Modules used from $root:");
233 1         6 visit_trace( \&show_trace_visitor, $root, 0, $output );
234              
235             # anything left?
236 1 50       3 if (%loaded) {
237 0         0 visit_trace( \&show_trace_visitor, $_, 0, $output ) for sort keys %loaded;
238             }
239              
240             # did we miss some modules?
241 1 50       10 if (my @missed
242 74 50       261 = sort grep { !exists $reported{$_} && $_ ne 'Devel/TraceUse.pm' }
243             keys %INC
244             )
245             {
246 1 50       5 $output->("Modules used, but not reported:") if @missed;
247 1         7 $output->(" $_") for @missed;
248             }
249              
250 1         8 dump_proxies($output);
251              
252 1 50       17 close $output_fh if defined $output_fh;
253             }
254              
255             # Install the final hook
256             # If perl runs with -c we want to dump
257             CHECK {
258             # "perl -c" ?
259 2 50   2   5056 dump_result() if $^C;
260             }
261              
262 2     2   2162924 END { dump_result() }
263              
264             1;
265             __END__