File Coverage

blib/lib/Test/Vars.pm
Criterion Covered Total %
statement 256 268 95.5
branch 73 94 77.6
condition 32 42 76.1
subroutine 29 30 96.6
pod 3 3 100.0
total 393 437 89.9


line stmt bran cond sub pod time code
1             package Test::Vars;
2 41     41   3118241 use 5.010_000;
  41         543  
3 41     41   235 use strict;
  41         75  
  41         1135  
4 41     41   204 use warnings;
  41         91  
  41         2669  
5              
6             our $VERSION = '0.015';
7              
8             our @EXPORT = qw(all_vars_ok test_vars vars_ok);
9              
10 41     41   21068 use parent qw(Test::Builder::Module);
  41         13394  
  41         278  
11              
12 41     41   2761 use B ();
  41         78  
  41         830  
13 41     41   25302 use ExtUtils::Manifest qw(maniread);
  41         489798  
  41         3733  
14 41     41   24518 use IO::Pipe;
  41         353578  
  41         1892  
15 41     41   339 use List::Util 1.33 qw(all);
  41         1032  
  41         4880  
16 41     41   31654 use Storable qw(freeze thaw);
  41         141883  
  41         3380  
17 41     41   389 use Symbol qw(qualify_to_ref);
  41         84  
  41         2609  
18              
19 41   50 41   295 use constant _VERBOSE => ($ENV{TEST_VERBOSE} || 0);
  41         94  
  41         4865  
20 41     41   637 use constant _OPpLVAL_INTRO => 128;
  41         91  
  41         50931  
21              
22             #use Devel::Peek;
23             #use Data::Dumper;
24             #$Data::Dumper::Indent = 1;
25              
26             sub all_vars_ok {
27 2     2 1 166 my(%args) = @_;
28              
29 2         24 my $builder = __PACKAGE__->builder;
30              
31 2 50       74 if(not -f $ExtUtils::Manifest::MANIFEST){
32 0         0 $builder->plan(skip_all => "No $ExtUtils::Manifest::MANIFEST ready");
33             }
34 2         8 my $manifest = maniread();
35 2         1524 my @libs = grep{ m{\A lib/ .* [.]pm \z}xms } keys %{$manifest};
  104         166  
  2         22  
36              
37 2 50       14 if (! @libs) {
38 0         0 $builder->plan(skip_all => "not lib/");
39             }
40              
41 2         14 $builder->plan(tests => scalar @libs);
42              
43 2         1412 local $Test::Builder::Level = $Test::Builder::Level + 1;
44 2         6 my $fail = 0;
45 2         6 foreach my $lib(@libs){
46 2 50       8 _vars_ok(\&_results_as_tests, $lib, \%args) or $fail++;
47             }
48              
49 1         91 return $fail == 0;
50             }
51              
52             sub _results_as_tests {
53 129     129   3661 my($file, $exit_code, $results) = @_;
54              
55 129         2220 local $Test::Builder::Level = $Test::Builder::Level + 1;
56              
57 129         8538 my $builder = __PACKAGE__->builder;
58 129         10780 my $is_ok = $builder->ok($exit_code == 0, $file);
59              
60 129         150454 for my $result (@$results) {
61 161         2471 my ($method, $message) = @$result;
62 161         2106 $builder->$method($message);
63             }
64              
65 129         66757 return $is_ok;
66             }
67              
68             sub test_vars {
69 9     9 1 12576 my($lib, $result_handler, %args) = @_;
70 9         78 return _vars_ok($result_handler, $lib, \%args);
71             }
72              
73             sub vars_ok {
74 154     154 1 171072 my($lib, %args) = @_;
75 154         556 local $Test::Builder::Level = $Test::Builder::Level + 1;
76 154         890 return _vars_ok(\&_results_as_tests, $lib, \%args);
77             }
78              
79             sub _vars_ok {
80 165     165   544 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 165         1639 $file =~ s{\\}{/}g;
87              
88 165         3913 my $pipe = IO::Pipe->new;
89 165         185644 my $pid = fork();
90 165 50       10239 if(defined $pid){
91 165 100       2069 if($pid != 0) { # self
92 135         8926 $pipe->reader;
93 135         666008 my $results = thaw(join('', <$pipe>));
94 135         41330213 waitpid $pid, 0;
95              
96 135         5677 return $result_handler->($file, $?, $results);
97             }
98             else { # child
99 30         3278 $pipe->writer;
100 30         8529 exit !_check_vars($file, $args, $pipe);
101             }
102             }
103             else {
104 0         0 die "fork failed: $!";
105             }
106             }
107              
108             sub _check_vars {
109 30     30   819 my($file, $args, $pipe) = @_;
110              
111 30         461 my @results;
112              
113 30         485 my $package = $file;
114              
115             # Looks like a file name. Turn it into a package name.
116 30 50       1875 if($file =~ /\./){
117 30         1324 $package =~ s{\A .* \b lib/ }{}xms;
118 30         671 $package =~ s{[.]pm \z}{}xms;
119 30         430 $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 30 100       675 if(ref $args->{ignore_vars} eq 'ARRAY'){
129 1         13 $args->{ignore_vars} = { map{ $_ => 1 } @{$args->{ignore_vars}} };
  1         57  
  1         28  
130             }
131              
132 30 50       1056 if(not exists $args->{ignore_vars}{'$self'}){
133 30         418 $args->{ignore_vars}{'$self'}++;
134             }
135              
136             # ensure library loaded
137             {
138 30     0   182 local $SIG{__WARN__} = sub{ }; # ignore warnings
  30         2441  
139              
140             # set PERLDB flags; see also perlvar
141 30         1012 local $^P = $^P | 0x200; # NAMEANON
142              
143 30         2037 local @INC = @INC;
144 30 50       634 if($file =~ s{\A (.*\b lib)/}{}xms){
145 30         1141 unshift @INC, $1;
146             }
147 30         391 eval { require $file };
  30         35474  
148              
149 30 100       9371 if($@){
150 2         15 $@ =~ s/\n .*//xms;
151 2         16 push @results, [diag => "Test::Vars ignores $file because: $@"];
152 2         37 _pipe_results($pipe, @results);
153 2         695 return 1;
154             }
155             }
156              
157 28         474 push @results, [note => "checking $package in $file ..."];
158             my $check_result = _check_into_stash(
159 28         185 *{qualify_to_ref('', $package)}{HASH}, $file, $args, \@results);
  28         815  
160              
161 28         221 _pipe_results($pipe, @results);
162 28         10090 return $check_result;
163             }
164              
165             sub _check_into_stash {
166 28     28   1552 my($stash, $file, $args, $results) = @_;
167 28         115 my $fail = 0;
168              
169 28         92 foreach my $key(sort keys %{$stash}){
  28         774  
170 107         557 my $ref = \$stash->{$key};
171              
172 107 50       232 if (ref ${$ref} eq 'CODE') {
  107         387  
173             # Reify the glob and let perl figure out what to put in
174             # GvFILE. This is needed for the optimization added in 5.27.6 that
175             # stores coderefs directly in the stash instead of in a typeglob
176             # in the stash.
177 41     41   394 no strict 'refs';
  41         108  
  41         33865  
178 0         0 () = *{B::svref_2object($stash)->NAME . "::$key"};
  0         0  
179             }
180              
181 107 50       745 next if ref($ref) ne 'GLOB';
182              
183 107         1685 my $gv = B::svref_2object($ref);
184              
185 107         226 my $hashref = *{$ref}{HASH};
  107         250  
186 107         168 my $coderef = *{$ref}{CODE};
  107         388  
187              
188 107 100 66     2356 if(($hashref || $coderef) && $gv->FILE =~ /\Q$file\E\z/xms){
      100        
189 48 50 33     329 if($hashref && B::svref_2object($hashref)->NAME){ # stash
    50          
190 0 0       0 if(not _check_into_stash(
191             $hashref, $file, $args, $results)){
192 0         0 $fail++;
193             }
194             }
195             elsif($coderef){
196 48 100       304 if(not _check_into_code($coderef, $args, $results)){
197 9         38 $fail++;
198             }
199             }
200             }
201             }
202              
203 28         167 return $fail == 0;
204             }
205              
206             sub _check_into_code {
207 48     48   174 my($coderef, $args, $results) = @_;
208              
209 48         528 my $cv = B::svref_2object($coderef);
210              
211             # If ROOT is null then the sub is a stub, and has no body for us to check.
212 48 50 33     3556 if($cv->XSUB || $cv->ROOT->isa('B::NULL')){
213 0         0 return 1;
214             }
215              
216 48         208 my %info;
217 48         497 _count_padvars($cv, \%info, $results);
218              
219 48         143 my $fail = 0;
220              
221 48         288 foreach my $cv_info(map { $info{$_} } sort keys %info){
  56         452  
222 56         188 my $pad = $cv_info->{pad};
223              
224 56         77 push @$results, [note => "looking into $cv_info->{name}"] if _VERBOSE > 1;
225              
226 56         138 foreach my $p(@{$pad}){
  56         443  
227 599 100 100     1784 next if !( defined $p && !$p->{outside} );
228              
229 217 100       507 if(! $p->{count}){
230 16 100       98 next if $args->{ignore_vars}{$p->{name}};
231              
232 12 100       71 if(my $cb = $args->{ignore_if}){
233 2         7 local $_ = $p->{name};
234 2 50       10 next if $cb->($_);
235             }
236              
237 10   50     63 my $c = $p->{context} || '';
238 10         164 push @$results, [diag => "$p->{name} is used once in $cv_info->{name} $c"];
239 10         54 $fail++;
240             }
241 0         0 elsif(_VERBOSE > 1){
242             push @$results, [note => "$p->{name} is used $p->{count} times"];
243             }
244             }
245             }
246              
247 48         403 return $fail == 0;
248              
249             }
250              
251             sub _pipe_results {
252 30     30   149 my ($pipe, @messages) = @_;
253 30         790 print $pipe freeze(\@messages);
254 30         7399 close $pipe;
255             }
256              
257             my @padops;
258             my $op_anoncode;
259             my $op_enteriter;
260             my $op_entereval; # string eval
261             my $op_null;
262             my @op_svusers;
263             BEGIN{
264 41     41   251 foreach my $op(qw(padsv padav padhv padcv match multideref subst)){
265 287         1729 $padops[B::opnumber($op)]++;
266             }
267             # blead commit 93bad3fd55489cbd split aelemfast into two ops.
268             # Prior to that, 'aelemfast' handled lexicals too.
269 41         221 my $aelemfast = B::opnumber('aelemfast_lex');
270 41 50       210 $padops[$aelemfast == -1 ? B::opnumber('aelemfast') : $aelemfast]++;
271              
272 41         281 $op_anoncode = B::opnumber('anoncode');
273 41         106 $padops[$op_anoncode]++;
274              
275 41         191 $op_enteriter = B::opnumber('enteriter');
276 41         81 $padops[$op_enteriter]++;
277              
278 41         263 $op_entereval = B::opnumber('entereval');
279 41         182 $padops[$op_entereval]++;
280              
281 41         152 $op_null = B::opnumber('null');
282              
283 41         119 foreach my $op(qw(srefgen refgen sassign aassign)){
284 164         25620 $op_svusers[B::opnumber($op)]++;
285             }
286             }
287              
288             sub _count_padvars {
289 56     56   180 my($cv, $global_info, $results) = @_;
290              
291 56         161 my %info;
292              
293 56         1228 my $padlist = $cv->PADLIST;
294              
295 56         522 my $padvars = $padlist->ARRAYelt(1);
296              
297 56         182 my @pad;
298 56         122 my $ix = 0;
299 56         951 foreach my $padname($padlist->ARRAYelt(0)->ARRAY){
300 752 50       3156 if($padname->can('PVX')){
301 752         2593 my $pv = $padname->PVX;
302              
303             # Under Perl 5.22.0+, $pv can end up as undef in some cases. With
304             # a threaded Perl, instead of undef we see an empty string.
305             #
306             # $pv can also end up as just '$' or '&'.
307 752 100 66     4134 if(defined $pv && length $pv && $pv ne '&' && $pv ne '$' && !($padname->FLAGS & B::SVpad_OUR)){
      100        
      66        
      100        
308 258         391 my %p;
309              
310 258         804 $p{name} = $pv;
311 258 100       991 $p{outside} = $padname->FLAGS & B::SVf_FAKE ? 1 : 0;
312 258 100       593 if($p{outside}){
313 44         84 $p{outside_padix} = $padname->PARENT_PAD_INDEX;
314             }
315 258         573 $p{padix} = $ix;
316              
317 258         772 $pad[$ix] = \%p;
318             }
319             }
320 752         1279 $ix++;
321             }
322              
323 56         619 my ( $cop_scan, $op_scan ) = _make_scan_subs(\@pad, $cv, $padvars, $global_info, $results, \%info);
324 56         226 local *B::COP::_scan_unused_vars;
325 56         731 *B::COP::_scan_unused_vars = $cop_scan;
326              
327 56         176 local *B::OP::_scan_unused_vars;
328 56         1816 *B::OP::_scan_unused_vars = $op_scan;
329              
330 56         1295 my $name = sprintf('&%s::%s', $cv->GV->STASH->NAME, $cv->GV->NAME);
331              
332 56         295 my $root = $cv->ROOT;
333 56 50       154 if(${$root}){
  56         384  
334 56         586 B::walkoptree($root, '_scan_unused_vars');
335             }
336             else{
337 0         0 push @$results, [note => "NULL body subroutine $name found"];
338             }
339              
340 56         1211 %info = (
341             pad => \@pad,
342             name => $name,
343             );
344              
345 56         186 return $global_info->{ ${$cv} } = \%info;
  56         841  
346             }
347              
348             sub _make_scan_subs {
349 56     56   202 my ($pad, $cv, $padvars, $global_info, $results, $info) = @_;
350              
351 56         145 my $cop;
352             my $cop_scan = sub {
353 490     490   2080 ($cop) = @_;
354 56         590 };
355              
356 56         140 my $stringy_eval_seen = 0;
357             my $op_scan = sub {
358 3635     3635   6622 my($op) = @_;
359              
360 3635 100       6210 return if $stringy_eval_seen;
361              
362 3632         9028 my $optype = $op->type;
363 3632 100       12973 return if !defined $padops[ $optype ];
364             # stringy eval could refer all the my variables
365 701 100       1392 if($optype == $op_entereval){
366 1         11 foreach my $p(@$pad){
367 5         17 $p->{count}++;
368             }
369 1         2 $stringy_eval_seen = 1;
370 1         5 return;
371             }
372              
373             # In Perl 5.22+, pad variables can be referred to in ops like
374             # MULTIDEREF, which show up as a B::UNOP_AUX object. This object can
375             # refer to multiple pad variables.
376 700 100       2395 if($op->isa('B::UNOP_AUX')) {
377 76         334 foreach my $i(grep {!ref}$ op->aux_list($cv)) {
  232         586  
378             # There is a bug in 5.24 with multideref aux_list where it can
379             # contain a value which is completely broken. It numifies to
380             # undef when used as an array index but "defined $i" will be
381             # true! We can detect it by comparing its stringified value to
382             # an empty string. This has been fixed in blead.
383 158 50       224 next unless do {
384 41     41   398 no warnings;
  41         88  
  41         26483  
385 158         448 "$i" ne q{};
386             };
387 158 100       392 $pad->[$i]{count}++
388             if $pad->[$i];
389             }
390 76         294 return;
391             }
392              
393 624         1776 my $targ = $op->targ;
394 624 100       1342 return if $targ == 0; # maybe foreach (...)
395              
396 616         927 my $p = $pad->[$targ];
397 616   100     2227 $p->{count} ||= 0;
398              
399 616 100 100     3589 if($optype == $op_anoncode){
    100          
    100          
400 8         32 my $anon_cv = $padvars->ARRAYelt($targ);
401 8 50       27 if($anon_cv->CvFLAGS & B::CVf_CLONE){
402 8         69 my $my_info = _count_padvars($anon_cv, $global_info, $results);
403              
404 8         19 $my_info->{outside} = $info;
405              
406 8         13 foreach my $p(@{$my_info->{pad}}){
  8         22  
407 132 100 100     319 if(defined $p && $p->{outside_padix}){
408 32         84 $pad->[ $p->{outside_padix} ]{count}++;
409             }
410             }
411             }
412 8         70 return;
413             }
414             elsif($optype == $op_enteriter or ($op->flags & B::OPf_WANT) == B::OPf_WANT_VOID){
415             # if $op is in void context, it is considered "not used"
416 63 100       298 if(_ckwarn_once($cop)){
417 61         713 $p->{context} = sprintf 'at %s line %d', $cop->file, $cop->line;
418 61         406 return; # skip
419             }
420             }
421             elsif($op->private & _OPpLVAL_INTRO){
422             # my($var) = @_;
423             # ^^^^ padsv/non-void context
424             # ^ sassign/void context
425             #
426             # We gather all of the sibling ops that are not NULL. If all of
427             # them are SV-using OPs (see the BEGIN block earlier) _and_ all of
428             # them are in VOID context, then the variable from the first op is
429             # being used once.
430 160         260 my @ops;
431 160   66     528 for(my $o = $op->next; ${$o} && ref($o) ne 'B::COP'; $o = $o->next){
  494         2033  
432 334 100       1663 push @ops, $o
433             unless $o->type == $op_null;
434             }
435              
436 160 100       1170 if (all {$op_svusers[$_->type] && ($_->flags & B::OPf_WANT) == B::OPf_WANT_VOID} @ops){
  162 100       1223  
437 108 50       294 if(_ckwarn_once($cop)){
438 108         919 $p->{context} = sprintf 'at %s line %d',
439             $cop->file, $cop->line;
440 108         874 return; # unused, but ok
441             }
442             }
443             }
444              
445 439         2034 $p->{count}++;
446 56         911 };
447              
448 56         334 return ($cop_scan, $op_scan);
449             }
450              
451             sub _ckwarn_once {
452 171     171   385 my($cop) = @_;
453              
454 171         927 my $w = $cop->warnings;
455 171 100       609 if(ref($w) eq 'B::SPECIAL'){
456 161         333 return $B::specialsv_name[ ${$w} ] !~ /WARN_NONE/;
  161         956  
457             }
458             else {
459 10         18 my $bits = ${$w->object_2svref};
  10         219  
460             # see warnings::__chk() and warnings::enabled()
461 10         81 return vec($bits, $warnings::Offsets{once}, 1);
462             }
463             }
464              
465             1;
466             __END__