File Coverage

blib/lib/Test/Vars.pm
Criterion Covered Total %
statement 251 261 96.1
branch 72 92 78.2
condition 31 42 73.8
subroutine 28 29 96.5
pod 3 3 100.0
total 385 427 90.1


line stmt bran cond sub pod time code
1             package Test::Vars;
2 38     38   570697 use 5.010_000;
  38         104  
3 38     38   152 use strict;
  38         42  
  38         758  
4 38     38   138 use warnings;
  38         62  
  38         1851  
5              
6             our $VERSION = '0.014';
7              
8             our @EXPORT = qw(all_vars_ok test_vars vars_ok);
9              
10 38     38   14992 use parent qw(Test::Builder::Module);
  38         8051  
  38         160  
11              
12 38     38   1799 use B ();
  38         53  
  38         562  
13 38     38   16467 use ExtUtils::Manifest qw(maniread);
  38         309569  
  38         2516  
14 38     38   15612 use IO::Pipe;
  38         232393  
  38         1270  
15 38     38   207 use List::Util 1.33 qw(all);
  38         721  
  38         3355  
16 38     38   20179 use Storable qw(freeze thaw);
  38         108451  
  38         2211  
17 38     38   963 use Symbol qw(qualify_to_ref);
  38         82  
  38         1994  
18              
19 38   50 38   160 use constant _VERBOSE => ($ENV{TEST_VERBOSE} || 0);
  38         24  
  38         2816  
20 38     38   198 use constant _OPpLVAL_INTRO => 128;
  38         38  
  38         49502  
21              
22             #use Devel::Peek;
23             #use Data::Dumper;
24             #$Data::Dumper::Indent = 1;
25              
26             sub all_vars_ok {
27 2     2 1 20 my(%args) = @_;
28              
29 2         38 my $builder = __PACKAGE__->builder;
30              
31 2 50       148 if(not -f $ExtUtils::Manifest::MANIFEST){
32 0         0 $builder->plan(skip_all => "No $ExtUtils::Manifest::MANIFEST ready");
33             }
34 2         14 my $manifest = maniread();
35 2         888 my @libs = grep{ m{\A lib/ .* [.]pm \z}xms } keys %{$manifest};
  100         138  
  2         32  
36              
37 2 50       24 if (! @libs) {
38 0         0 $builder->plan(skip_all => "not lib/");
39             }
40              
41 2         16 $builder->plan(tests => scalar @libs);
42              
43 2         880 local $Test::Builder::Level = $Test::Builder::Level + 1;
44 2         6 my $fail = 0;
45 2         6 foreach my $lib(@libs){
46 2 50       12 _vars_ok(\&_results_as_tests, $lib, \%args) or $fail++;
47             }
48              
49 1         81 return $fail == 0;
50             }
51              
52             sub _results_as_tests {
53 127     127   1450 my($file, $exit_code, $results) = @_;
54              
55 127         629 local $Test::Builder::Level = $Test::Builder::Level + 1;
56              
57 127         2967 my $builder = __PACKAGE__->builder;
58 127         3832 my $is_ok = $builder->ok($exit_code == 0, $file);
59              
60 127         70699 for my $result (@$results) {
61 159         1043 my ($method, $message) = @$result;
62 159         810 $builder->$method($message);
63             }
64              
65 127         18367 return $is_ok;
66             }
67              
68             sub test_vars {
69 9     9 1 8921 my($lib, $result_handler, %args) = @_;
70 9         39 return _vars_ok($result_handler, $lib, \%args);
71             }
72              
73             sub vars_ok {
74 150     150 1 73952 my($lib, %args) = @_;
75 150         315 local $Test::Builder::Level = $Test::Builder::Level + 1;
76 150         1125 return _vars_ok(\&_results_as_tests, $lib, \%args);
77             }
78              
79             sub _vars_ok {
80 161     161   334 my($result_handler, $file, $args) = @_;
81              
82             # Perl sometimes produces Unix style paths even on Windows, which can lead
83             # to us producing error messages with a path like "lib\foo/bar.pm", which
84             # is really confusing. It's simpler to just use Unix style everywhere
85             # internally.
86 161         641 $file =~ s{\\}{/}g;
87              
88 161         1796 my $pipe = IO::Pipe->new;
89 161         102594 my $pid = fork();
90 161 50       4460 if(defined $pid){
91 161 100       1316 if($pid != 0) { # self
92 133         4444 $pipe->reader;
93 133         505607 my $results = thaw(join('', <$pipe>));
94 133         29282957 waitpid $pid, 0;
95              
96 133         2083 return $result_handler->($file, $?, $results);
97             }
98             else { # child
99 28         1839 $pipe->writer;
100 28         4331 exit !_check_vars($file, $args, $pipe);
101             }
102             }
103             else {
104 0         0 die "fork failed: $!";
105             }
106             }
107              
108             sub _check_vars {
109 28     28   278 my($file, $args, $pipe) = @_;
110              
111 28         207 my @results;
112              
113 28         145 my $package = $file;
114              
115             # Looks like a file name. Turn it into a package name.
116 28 50       1007 if($file =~ /\./){
117 28         907 $package =~ s{\A .* \b lib/ }{}xms;
118 28         267 $package =~ s{[.]pm \z}{}xms;
119 28         177 $package =~ s{/}{::}g;
120             }
121              
122             # Looks like a package name. Make a file name from it.
123             else{
124 0         0 $file .= '.pm';
125 0         0 $file =~ s{::}{/}g;
126             }
127              
128 28 100       505 if(ref $args->{ignore_vars} eq 'ARRAY'){
129 1         5 $args->{ignore_vars} = { map{ $_ => 1 } @{$args->{ignore_vars}} };
  1         19  
  1         17  
130             }
131              
132 28 50       974 if(not exists $args->{ignore_vars}{'$self'}){
133 28         192 $args->{ignore_vars}{'$self'}++;
134             }
135              
136             # ensure library loaded
137             {
138 28     0   162 local $SIG{__WARN__} = sub{ }; # ignore warnings
  28         1588  
139              
140             # set PERLDB flags; see also perlvar
141 28         637 local $^P = $^P | 0x200; # NAMEANON
142              
143 28         1171 local @INC = @INC;
144 28 50       420 if($file =~ s{\A (.*\b lib)/}{}xms){
145 28         534 unshift @INC, $1;
146             }
147 28         199 eval { require $file };
  28         21243  
148              
149 28 100       5770 if($@){
150 2         19 $@ =~ s/\n .*//xms;
151 2         24 push @results, [diag => "Test::Vars ignores $file because: $@"];
152 2         28 _pipe_results($pipe, @results);
153 2         281 return 1;
154             }
155             }
156              
157 26         180 push @results, [note => "checking $package in $file ..."];
158             my $check_result = _check_into_stash(
159 26         66 *{qualify_to_ref('', $package)}{HASH}, $file, $args, \@results);
  26         419  
160              
161 26         121 _pipe_results($pipe, @results);
162 26         4115 return $check_result;
163             }
164              
165             sub _check_into_stash {
166 26     26   847 my($stash, $file, $args, $results) = @_;
167 26         68 my $fail = 0;
168              
169 26         50 foreach my $key(sort keys %{$stash}){
  26         542  
170 109         263 my $ref = \$stash->{$key};
171              
172 109 50       278 next if ref($ref) ne 'GLOB';
173              
174 109         826 my $gv = B::svref_2object($ref);
175              
176 109         106 my $hashref = *{$ref}{HASH};
  109         168  
177 109         109 my $coderef = *{$ref}{CODE};
  109         168  
178              
179 109 100 66     2272 if(($hashref || $coderef) && $gv->FILE =~ /\Q$file\E\z/xms){
      100        
180 46 50 33     284 if($hashref && B::svref_2object($hashref)->NAME){ # stash
    50          
181 0 0       0 if(not _check_into_stash(
182             $hashref, $file, $args, $results)){
183 0         0 $fail++;
184             }
185             }
186             elsif($coderef){
187 46 100       188 if(not _check_into_code($coderef, $args, $results)){
188 9         19 $fail++;
189             }
190             }
191             }
192             }
193              
194 26         125 return $fail == 0;
195             }
196              
197             sub _check_into_code {
198 46     46   73 my($coderef, $args, $results) = @_;
199              
200 46         316 my $cv = B::svref_2object($coderef);
201              
202             # If ROOT is null then the sub is a stub, and has no body for us to check.
203 46 50 33     1995 if($cv->XSUB || $cv->ROOT->isa('B::NULL')){
204 0         0 return 1;
205             }
206              
207 46         143 my %info;
208 46         244 _count_padvars($cv, \%info, $results);
209              
210 46         84 my $fail = 0;
211              
212 46         160 foreach my $cv_info(map { $info{$_} } sort keys %info){
  54         228  
213 54         138 my $pad = $cv_info->{pad};
214              
215 54         47 push @$results, [note => "looking into $cv_info->{name}"] if _VERBOSE > 1;
216              
217 54         59 foreach my $p(@{$pad}){
  54         292  
218 577 100 100     1580 next if !( defined $p && !$p->{outside} );
219              
220 213 100       351 if(! $p->{count}){
221 15 100       232 next if $args->{ignore_vars}{$p->{name}};
222              
223 12 100       99 if(my $cb = $args->{ignore_if}){
224 2         5 local $_ = $p->{name};
225 2 50       11 next if $cb->($_);
226             }
227              
228 10   50     29 my $c = $p->{context} || '';
229 10         92 push @$results, [diag => "$p->{name} is used once in $cv_info->{name} $c"];
230 10         25 $fail++;
231             }
232 0         0 elsif(_VERBOSE > 1){
233             push @$results, [note => "$p->{name} is used $p->{count} times"];
234             }
235             }
236             }
237              
238 46         311 return $fail == 0;
239              
240             }
241              
242             sub _pipe_results {
243 28     28   62 my ($pipe, @messages) = @_;
244 28         450 print $pipe freeze(\@messages);
245 28         4398 close $pipe;
246             }
247              
248             my @padops;
249             my $op_anoncode;
250             my $op_enteriter;
251             my $op_entereval; # string eval
252             my $op_null;
253             my @op_svusers;
254             BEGIN{
255 38     38   96 foreach my $op(qw(padsv padav padhv match multideref subst)){
256 228         631 $padops[B::opnumber($op)]++;
257             }
258             # blead commit 93bad3fd55489cbd split aelemfast into two ops.
259             # Prior to that, 'aelemfast' handled lexicals too.
260 38         122 my $aelemfast = B::opnumber('aelemfast_lex');
261 38 50       111 $padops[$aelemfast == -1 ? B::opnumber('aelemfast') : $aelemfast]++;
262              
263 38         83 $op_anoncode = B::opnumber('anoncode');
264 38         44 $padops[$op_anoncode]++;
265              
266 38         149 $op_enteriter = B::opnumber('enteriter');
267 38         872 $padops[$op_enteriter]++;
268              
269 38         213 $op_entereval = B::opnumber('entereval');
270 38         38 $padops[$op_entereval]++;
271              
272 38         52 $op_null = B::opnumber('null');
273              
274 38         39 foreach my $op(qw(srefgen refgen sassign aassign)){
275 152         15075 $op_svusers[B::opnumber($op)]++;
276             }
277             }
278              
279             sub _count_padvars {
280 54     54   90 my($cv, $global_info, $results) = @_;
281              
282 54         57 my %info;
283              
284 54         434 my $padlist = $cv->PADLIST;
285              
286 54         295 my $padvars = $padlist->ARRAYelt(1);
287              
288 54         63 my @pad;
289 54         67 my $ix = 0;
290 54         655 foreach my $padname($padlist->ARRAYelt(0)->ARRAY){
291 729 50       1707 if($padname->can('PVX')){
292 729         1536 my $pv = $padname->PVX;
293              
294             # Under Perl 5.22.0+, $pv can end up as undef in some cases. With
295             # a threaded Perl, instead of undef we see an empty string.
296             #
297             # $pv can also end up as just '$' or '&'.
298 729 100 66     3660 if(defined $pv && length $pv && $pv ne '&' && $pv ne '$' && !($padname->FLAGS & B::SVpad_OUR)){
      100        
      66        
      100        
299 254         199 my %p;
300              
301 254         583 $p{name} = $pv;
302 254 100       629 $p{outside} = $padname->FLAGS & B::SVf_FAKE ? 1 : 0;
303 254 100       421 if($p{outside}){
304 44         70 $p{outside_padix} = $padname->PARENT_PAD_INDEX;
305             }
306 254         350 $p{padix} = $ix;
307              
308 254         543 $pad[$ix] = \%p;
309             }
310             }
311 729         683 $ix++;
312             }
313              
314 54         413 my ( $cop_scan, $op_scan ) = _make_scan_subs(\@pad, $cv, $padvars, $global_info, $results, \%info);
315 54         146 local *B::COP::_scan_unused_vars;
316 54         313 *B::COP::_scan_unused_vars = $cop_scan;
317              
318 54         127 local *B::OP::_scan_unused_vars;
319 54         787 *B::OP::_scan_unused_vars = $op_scan;
320              
321 54         1192 my $name = sprintf('&%s::%s', $cv->GV->STASH->NAME, $cv->GV->NAME);
322              
323 54         178 my $root = $cv->ROOT;
324 54 50       53 if(${$root}){
  54         233  
325 54         519 B::walkoptree($root, '_scan_unused_vars');
326             }
327             else{
328 0         0 push @$results, [note => "NULL body subroutine $name found"];
329             }
330              
331 54         809 %info = (
332             pad => \@pad,
333             name => $name,
334             );
335              
336 54         85 return $global_info->{ ${$cv} } = \%info;
  54         622  
337             }
338              
339             sub _make_scan_subs {
340 54     54   93 my ($pad, $cv, $padvars, $global_info, $results, $info) = @_;
341              
342 54         68 my $cop;
343             my $cop_scan = sub {
344 477     477   1276 ($cop) = @_;
345 54         297 };
346              
347 54         56 my $stringy_eval_seen = 0;
348             my $op_scan = sub {
349 3530     3530   2936 my($op) = @_;
350              
351 3530 100       4424 return if $stringy_eval_seen;
352              
353 3527         6308 my $optype = $op->type;
354 3527 100       9904 return if !defined $padops[ $optype ];
355             # stringy eval could refer all the my variables
356 688 100       1031 if($optype == $op_entereval){
357 1         7 foreach my $p(@$pad){
358 5         13 $p->{count}++;
359             }
360 1         1 $stringy_eval_seen = 1;
361 1         3 return;
362             }
363              
364             # In Perl 5.22+, pad variables can be referred to in ops like
365             # MULTIDEREF, which show up as a B::UNOP_AUX object. This object can
366             # refer to multiple pad variables.
367 687 100       2102 if($op->isa('B::UNOP_AUX')) {
368 74         221 foreach my $i(grep {!ref}$ op->aux_list($cv)) {
  226         387  
369             # There is a bug in 5.24 with multideref aux_list where it can
370             # contain a value which is completely broken. It numifies to
371             # undef when used as an array index but "defined $i" will be
372             # true! We can detect it by comparing its stringified value to
373             # an empty string. This has been fixed in blead.
374 154 50       94 next unless do {
375 38     38   185 no warnings;
  38         38  
  38         16490  
376 154         340 "$i" ne q{};
377             };
378 154 100       327 $pad->[$i]{count}++
379             if $pad->[$i];
380             }
381 74         301 return;
382             }
383              
384 613         1209 my $targ = $op->targ;
385 613 100       898 return if $targ == 0; # maybe foreach (...)
386              
387 605         555 my $p = $pad->[$targ];
388 605   100     1866 $p->{count} ||= 0;
389              
390 605 100 100     3672 if($optype == $op_anoncode){
    100          
    100          
391 8         28 my $anon_cv = $padvars->ARRAYelt($targ);
392 8 50       29 if($anon_cv->CvFLAGS & B::CVf_CLONE){
393 8         68 my $my_info = _count_padvars($anon_cv, $global_info, $results);
394              
395 8         28 $my_info->{outside} = $info;
396              
397 8         9 foreach my $p(@{$my_info->{pad}}){
  8         18  
398 132 100 66     333 if(defined $p && $p->{outside_padix}){
399 32         46 $pad->[ $p->{outside_padix} ]{count}++;
400             }
401             }
402             }
403 8         75 return;
404             }
405             elsif($optype == $op_enteriter or ($op->flags & B::OPf_WANT) == B::OPf_WANT_VOID){
406             # if $op is in void context, it is considered "not used"
407 61 100       128 if(_ckwarn_once($cop)){
408 59         447 $p->{context} = sprintf 'at %s line %d', $cop->file, $cop->line;
409 59         209 return; # skip
410             }
411             }
412             elsif($op->private & _OPpLVAL_INTRO){
413             # my($var) = @_;
414             # ^^^^ padsv/non-void context
415             # ^ sassign/void context
416             #
417             # We gather all of the sibling ops that are not NULL. If all of
418             # them are SV-using OPs (see the BEGIN block earlier) _and_ all of
419             # them are in VOID context, then the variable from the first op is
420             # being used once.
421 159         128 my @ops;
422 159   66     364 for(my $o = $op->next; ${$o} && ref($o) ne 'B::COP'; $o = $o->next){
  492         1826  
423 333 100       1443 push @ops, $o
424             unless $o->type == $op_null;
425             }
426              
427 159 100       960 if (all {$op_svusers[$_->type] && ($_->flags & B::OPf_WANT) == B::OPf_WANT_VOID} @ops){
  161 100       1442  
428 107 50       146 if(_ckwarn_once($cop)){
429 107         796 $p->{context} = sprintf 'at %s line %d',
430             $cop->file, $cop->line;
431 107         745 return; # unused, but ok
432             }
433             }
434             }
435              
436 431         1609 $p->{count}++;
437 54         560 };
438              
439 54         170 return ($cop_scan, $op_scan);
440             }
441              
442             sub _ckwarn_once {
443 168     168   174 my($cop) = @_;
444              
445 168         586 my $w = $cop->warnings;
446 168 100       337 if(ref($w) eq 'B::SPECIAL'){
447 158         162 return $B::specialsv_name[ ${$w} ] !~ /WARN_NONE/;
  158         785  
448             }
449             else {
450 10         18 my $bits = ${$w->object_2svref};
  10         111  
451             # see warnings::__chk() and warnings::enabled()
452 10         54 return vec($bits, $warnings::Offsets{once}, 1);
453             }
454             }
455              
456             1;
457             __END__