File Coverage

blib/lib/Perl/Critic/Policy/Subroutines/RequireArgUnpacking.pm
Criterion Covered Total %
statement 41 129 31.7
branch 7 106 6.6
condition 0 57 0.0
subroutine 13 23 56.5
pod 4 5 80.0
total 65 320 20.3


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Subroutines::RequireArgUnpacking;
2              
3 40     40   25604 use 5.010001;
  40         156  
4 40     40   169 use strict;
  40         82  
  40         658  
5 40     40   127 use warnings;
  40         67  
  40         1355  
6              
7 40     40   162 use Readonly;
  40         60  
  40         2090  
8              
9 40         1986 use Perl::Critic::Utils qw(
10             :booleans :characters :classification hashify :severities
11 40     40   195 );
  40         75  
12 40     40   17299 use parent 'Perl::Critic::Policy';
  40         90  
  40         241  
13              
14             our $VERSION = '1.156';
15              
16             #-----------------------------------------------------------------------------
17              
18             Readonly::Scalar my $AT => q{@};
19             Readonly::Scalar my $AT_ARG => q{@_}; ## no critic (InterpolationOfMetachars)
20             Readonly::Scalar my $DEREFERENCE => q{->};
21             Readonly::Scalar my $DOLLAR => q{$};
22             Readonly::Scalar my $DOLLAR_ARG => q{$_}; ## no critic (InterpolationOfMetaChars)
23              
24             Readonly::Scalar my $DESC => qq{Always unpack $AT_ARG first};
25             Readonly::Scalar my $EXPL => [178];
26              
27             #-----------------------------------------------------------------------------
28              
29             sub supported_parameters {
30             return (
31             {
32 94     94 0 1592 name => 'short_subroutine_statements',
33             description =>
34             'The number of statements to allow without unpacking.',
35             default_string => '0',
36             behavior => 'integer',
37             integer_minimum => 0,
38             },
39             {
40             name => 'allow_subscripts',
41             description =>
42             'Should unpacking from array slices and elements be allowed?',
43             default_string => $FALSE,
44             behavior => 'boolean',
45             },
46             {
47             name => 'allow_delegation_to',
48             description =>
49             'Allow the usual delegation idiom to these namespaces/subroutines',
50             behavior => 'string list',
51             list_always_present_values => [ qw< SUPER:: NEXT:: > ],
52             },
53             {
54             name => 'allow_closures',
55             description => 'Allow unpacking by a closure',
56             default_string => $FALSE,
57             behavior => 'boolean',
58             },
59             );
60             }
61              
62 75     75 1 230 sub default_severity { return $SEVERITY_HIGH }
63 86     86 1 271 sub default_themes { return qw( core pbp maintenance ) }
64 33     33 1 77 sub applies_to { return 'PPI::Statement::Sub' }
65              
66             #-----------------------------------------------------------------------------
67              
68             sub violates {
69 5     5 1 12 my ( $self, $elem, undef ) = @_;
70              
71             # forward declaration?
72 5 50       17 return if not $elem->block;
73              
74 5         101 my @statements = $elem->block->schildren;
75              
76             # empty sub?
77 5 50       151 return if not @statements;
78              
79             # Don't apply policy to short subroutines
80              
81             # Should we instead be doing a find() for PPI::Statement
82             # instances? That is, should we count all statements instead of
83             # just top-level statements?
84 5 50       35 return if $self->{_short_subroutine_statements} >= @statements;
85              
86             # look for explicit dereferences of @_, including '$_[0]'
87             # You may use "... = @_;" in the first paragraph of the sub
88             # Don't descend into nested or anonymous subs
89 5         11 my $state = 'unpacking'; # still in unpacking paragraph
90 5         9 for my $statement (@statements) {
91              
92 7         26 my @magic = _get_arg_symbols($statement);
93              
94 7         112 my $saw_unpack = $FALSE;
95              
96             MAGIC:
97 7         16 for my $magic (@magic) {
98             # allow conditional checks on the size of @_
99 0 0       0 next MAGIC if _is_size_check($magic);
100              
101 0 0       0 if ('unpacking' eq $state) {
102 0 0       0 if ($self->_is_unpack($magic)) {
103 0         0 $saw_unpack = $TRUE;
104 0         0 next MAGIC;
105             }
106             }
107              
108             # allow @$_[] construct in "... for ();"
109             # Check for "print @$_[] for ()" construct (rt39601)
110             next MAGIC
111 0 0 0     0 if _is_cast_of_array($magic) and _is_postfix_foreach($magic);
112              
113             # allow $$_[], which is equivalent to $_->[] and not a use
114             # of @_ at all.
115             next MAGIC
116 0 0       0 if _is_cast_of_scalar( $magic );
117              
118             # allow delegation of the form "$self->SUPER::foo( @_ );"
119             next MAGIC
120 0 0       0 if $self->_is_delegation( $magic );
121              
122             # If we make it this far, it is a violation
123 0         0 return $self->violation( $DESC, $EXPL, $elem );
124             }
125 7 50       16 if (not $saw_unpack) {
126 7         19 $state = 'post_unpacking';
127             }
128             }
129 5         16 return; # OK
130             }
131              
132             sub _is_unpack {
133 0     0   0 my ($self, $magic) = @_;
134              
135 0         0 my $prev = $magic->sprevious_sibling();
136 0         0 my $next = $magic->snext_sibling();
137              
138             # If we have a subscript, we're dealing with an array slice on @_
139             # or an array element of @_. See RT #34009.
140 0 0 0     0 if ( $next and $next->isa('PPI::Structure::Subscript') ) {
141 0 0       0 $self->{_allow_subscripts} or return;
142 0         0 $next = $next->snext_sibling;
143             }
144              
145 0 0 0     0 return $TRUE if
      0        
      0        
      0        
146             $prev
147             and $prev->isa('PPI::Token::Operator')
148             and is_assignment_operator($prev->content())
149             and (
150             not $next
151             or $next->isa('PPI::Token::Structure')
152             and $SCOLON eq $next->content()
153             );
154 0         0 return;
155             }
156              
157             sub _is_size_check {
158 0     0   0 my ($magic) = @_;
159              
160             # No size check on $_[0]. RT #34009.
161 0 0       0 $AT eq $magic->raw_type or return;
162              
163 0         0 my $prev = $magic->sprevious_sibling;
164 0         0 my $next = $magic->snext_sibling;
165              
166 0 0 0     0 if ( $prev || $next ) {
167              
168 0 0 0     0 return $TRUE
169             if _legal_before_size_check( $prev )
170             and _legal_after_size_check( $next );
171             }
172              
173 0         0 my $parent = $magic;
174             {
175 0 0       0 $parent = $parent->parent()
  0         0  
176             or return;
177 0         0 $prev = $parent->sprevious_sibling();
178 0         0 $next = $parent->snext_sibling();
179 0 0 0     0 $prev
180             or $next
181             or redo;
182             } # until ( $prev || $next );
183              
184 0 0       0 return $TRUE
185             if $parent->isa( 'PPI::Structure::Condition' );
186              
187 0         0 return;
188             }
189              
190             {
191              
192             Readonly::Hash my %LEGAL_NEXT_OPER => hashify(
193             qw{ && || == != > >= < <= and or } );
194              
195             Readonly::Hash my %LEGAL_NEXT_STRUCT => hashify( qw{ ; } );
196              
197             sub _legal_after_size_check {
198 0     0   0 my ( $next ) = @_;
199              
200 0 0       0 $next
201             or return $TRUE;
202              
203             $next->isa( 'PPI::Token::Operator' )
204 0 0       0 and return $LEGAL_NEXT_OPER{ $next->content() };
205              
206             $next->isa( 'PPI::Token::Structure' )
207 0 0       0 and return $LEGAL_NEXT_STRUCT{ $next->content() };
208              
209 0         0 return;
210             }
211             }
212              
213             {
214              
215             Readonly::Hash my %LEGAL_PREV_OPER => hashify(
216             qw{ && || ! == != > >= < <= and or not } );
217              
218             Readonly::Hash my %LEGAL_PREV_WORD => hashify(
219             qw{ if unless } );
220              
221             sub _legal_before_size_check {
222 0     0   0 my ( $prev ) = @_;
223              
224 0 0       0 $prev
225             or return $TRUE;
226              
227             $prev->isa( 'PPI::Token::Operator' )
228 0 0       0 and return $LEGAL_PREV_OPER{ $prev->content() };
229              
230             $prev->isa( 'PPI::Token::Word' )
231 0 0       0 and return $LEGAL_PREV_WORD{ $prev->content() };
232              
233 0         0 return;
234             }
235              
236             }
237              
238             sub _is_postfix_foreach {
239 0     0   0 my ($magic) = @_;
240              
241 0         0 my $sibling = $magic;
242 0         0 while ( $sibling = $sibling->snext_sibling ) {
243 0 0 0     0 return $TRUE
244             if
245             $sibling->isa('PPI::Token::Word')
246             and $sibling =~ m< \A for (?:each)? \z >xms;
247             }
248 0         0 return;
249             }
250              
251             sub _is_cast_of_array {
252 0     0   0 my ($magic) = @_;
253              
254 0         0 my $prev = $magic->sprevious_sibling;
255              
256 0 0 0     0 return $TRUE
      0        
257             if ( $prev && $prev->content() eq $AT )
258             and $prev->isa('PPI::Token::Cast');
259 0         0 return;
260             }
261              
262             # This subroutine recognizes (e.g.) $$_[0]. This is a use of $_ (equivalent to
263             # $_->[0]), not @_.
264              
265             sub _is_cast_of_scalar {
266 0     0   0 my ($magic) = @_;
267              
268 0         0 my $prev = $magic->sprevious_sibling;
269 0         0 my $next = $magic->snext_sibling;
270              
271 0   0     0 return $DOLLAR_ARG eq $magic->content() &&
272             $prev && $prev->isa('PPI::Token::Cast') &&
273             $DOLLAR eq $prev->content() &&
274             $next && $next->isa('PPI::Structure::Subscript');
275             }
276              
277             # A literal @_ is allowed as the argument for a delegation.
278             # An example of the idiom we are looking for is $self->SUPER::foo(@_).
279             # The argument list of (@_) is required; no other use of @_ is allowed.
280              
281             sub _is_delegation {
282 0     0   0 my ($self, $magic) = @_;
283              
284 0 0       0 $AT_ARG eq $magic->content() or return; # Not a literal '@_'.
285 0 0       0 my $parent = $magic->parent() # Don't know what to do with
286             or return; # orphans.
287 0 0       0 $parent->isa( 'PPI::Statement::Expression' )
288             or return; # Parent must be expression.
289 0 0       0 1 == $parent->schildren() # '@_' must stand alone in
290             or return; # its expression.
291 0 0       0 $parent = $parent->parent() # Still don't know what to do
292             or return; # with orphans.
293 0 0       0 $parent->isa ( 'PPI::Structure::List' )
294             or return; # Parent must be a list.
295 0 0       0 1 == $parent->schildren() # '@_' must stand alone in
296             or return; # the argument list.
297 0 0       0 my $subroutine_name = $parent->sprevious_sibling()
298             or return; # Missing sub name.
299 0 0 0     0 if ( $subroutine_name->isa( 'PPI::Token::Word' ) ) {
    0          
300 0 0       0 $self->{_allow_delegation_to}{$subroutine_name}
301             and return 1;
302 0 0       0 my ($subroutine_namespace) = $subroutine_name =~ m/ \A ( .* ::) \w+ \z /smx
303             or return;
304 0         0 return $self->{_allow_delegation_to}{$subroutine_namespace};
305             } elsif ( $self->{_allow_closures} &&
306             _is_dereference_operator( $subroutine_name ) ) {
307 0         0 my $prev_sib = $subroutine_name;
308             { # Single-iteration loop
309 0 0       0 $prev_sib = $prev_sib->sprevious_sibling()
  0         0  
310             or return;
311 0 0       0 ( $prev_sib->isa( 'PPI::Structure::Subscript' ||
312             _is_dereference_operator( $prev_sib ) ) )
313             and redo;
314             }
315 0         0 return $prev_sib->isa( 'PPI::Token::Symbol' );
316             }
317 0         0 return;
318             }
319              
320             sub _is_dereference_operator {
321 0     0   0 my ( $elem ) = @_;
322 0 0       0 $elem
323             or return;
324 0 0       0 $elem->isa( 'PPI::Token::Operator' )
325             or return;
326 0         0 return $DEREFERENCE eq $elem->content();
327             }
328              
329              
330             sub _get_arg_symbols {
331 7     7   13 my ($statement) = @_;
332              
333 7 50       12 return grep {$AT_ARG eq $_->symbol} @{$statement->find(\&_magic_finder) || []};
  0         0  
  7         34  
334             }
335              
336             sub _magic_finder {
337             # Find all @_ and $_[\d+] not inside of nested subs
338 39     39   411 my (undef, $elem) = @_;
339 39 50       136 return $TRUE if $elem->isa('PPI::Token::Magic'); # match
340              
341 39 50       127 if ($elem->isa('PPI::Structure::Block')) {
342             # don't descend into a nested named sub
343 0 0       0 return if $elem->statement->isa('PPI::Statement::Sub');
344              
345             # don't descend into a nested anon sub, either.
346 0 0       0 return if _is_anon_sub( $elem );
347              
348             }
349              
350 39         71 return $FALSE; # no match, descend
351             }
352              
353             # Detecting anonymous subs is hard, partly because PPI's parse of them, at
354             # least as of 1.220, appears to be a bit dodgy.
355             sub _is_anon_sub {
356 0     0     my ( $elem ) = @_;
357              
358             # If we have no previous element, we can not be an anonymous sub.
359 0 0         my $prev = $elem->sprevious_sibling()
360             or return $FALSE;
361              
362             # The simple case.
363 0 0 0       return $TRUE if $prev->isa( 'PPI::Token::Word' )
364             and 'sub' eq $prev->content();
365              
366             # Skip possible subroutine attributes. These appear as words (the names)
367             # or lists (the arguments, if any), or actual attributes (depending on how
368             # PPI handles them). A colon is required before the first, and is optional
369             # in between.
370 0   0       while ( $prev->isa( 'PPI::Token::Attribute' )
      0        
371             or $prev->isa( 'PPI::Token::Operator' )
372             and q<:> eq $prev->content() ) {
373              
374             # Grab the previous significant sib. If there is none, we can not
375             # be an anonymous sub with attributes.
376 0 0         return $FALSE if not $prev = $prev->sprevious_sibling();
377             }
378              
379             # At this point we may have a prototype. Skip that too, but there needs to
380             # be something before it.
381 0 0 0       return $FALSE if $prev->isa( 'PPI::Token::Prototype' )
382             and not $prev = $prev->sprevious_sibling();
383              
384             # Finally, we can find out if we're a sub
385 0 0 0       return $TRUE if $prev->isa( 'PPI::Token::Word' )
386             and 'sub' eq $prev->content();
387              
388             # We are out of options. At this point we can not possibly be an anon sub.
389 0           return $FALSE;
390             }
391              
392              
393             1;
394              
395             __END__
396              
397             #-----------------------------------------------------------------------------
398              
399             =pod
400              
401             =for stopwords Params::Validate
402              
403             =head1 NAME
404              
405             Perl::Critic::Policy::Subroutines::RequireArgUnpacking - Always unpack C<@_> first.
406              
407             =head1 AFFILIATION
408              
409             This Policy is part of the core L<Perl::Critic|Perl::Critic>
410             distribution.
411              
412              
413             =head1 DESCRIPTION
414              
415             Subroutines that use C<@_> directly instead of unpacking the arguments
416             to local variables first have two major problems. First, they are
417             very hard to read. If you're going to refer to your variables by
418             number instead of by name, you may as well be writing assembler code!
419             Second, C<@_> contains aliases to the original variables! If you
420             modify the contents of a C<@_> entry, then you are modifying the
421             variable outside of your subroutine. For example:
422              
423             sub print_local_var_plus_one {
424             my ($var) = @_;
425             print ++$var;
426             }
427             sub print_var_plus_one {
428             print ++$_[0];
429             }
430              
431             my $x = 2;
432             print_local_var_plus_one($x); # prints "3", $x is still 2
433             print_var_plus_one($x); # prints "3", $x is now 3 !
434             print $x; # prints "3"
435              
436             This is spooky action-at-a-distance and is very hard to debug if it's
437             not intentional and well-documented (like C<chop> or C<chomp>).
438              
439             An exception is made for the usual delegation idiom C<<
440             $object->SUPER::something( @_ ) >>. Only C<SUPER::> and C<NEXT::> are
441             recognized (though this is configurable) and the argument list for the
442             delegate must consist only of C<< ( @_ ) >>.
443              
444             =head1 CONFIGURATION
445              
446             This policy is lenient for subroutines which have C<N> or fewer
447             top-level statements, where C<N> defaults to ZERO. You can override
448             this to set it to a higher number with the
449             C<short_subroutine_statements> setting. This is very much not
450             recommended but perhaps you REALLY need high performance. To do this,
451             put entries in a F<.perlcriticrc> file like this:
452              
453             [Subroutines::RequireArgUnpacking]
454             short_subroutine_statements = 2
455              
456             By default this policy does not allow you to specify array subscripts
457             when you unpack arguments (i.e. by an array slice or by referencing
458             individual elements). Should you wish to permit this, you can do so
459             using the C<allow_subscripts> setting. This defaults to false. You can
460             set it true like this:
461              
462             [Subroutines::RequireArgUnpacking]
463             allow_subscripts = 1
464              
465             The delegation logic can be configured to allow delegation other than to
466             C<SUPER::> or C<NEXT::>. The configuration item is
467             C<allow_delegation_to>, and it takes a space-delimited list of allowed
468             delegates. If a given delegate ends in a double colon, anything in the
469             given namespace is allowed. If it does not, only that subroutine is
470             allowed. For example, to allow C<next::method> from C<Class::C3> and
471             _delegate from the current namespace in addition to SUPER and NEXT, the
472             following configuration could be used:
473              
474             [Subroutines::RequireArgUnpacking]
475             allow_delegation_to = next::method _delegate
476              
477             Argument validation tools such as L<Params::Validate|Params::Validate> generate a closure which is
478             used to unpack and validate the arguments of a subroutine. In order to
479             recognize closures as a valid way to unpack arguments you must enable them
480             explicitly:
481              
482             [Subroutines::RequireArgUnpacking]
483             allow_closures = 1
484              
485             =head1 CAVEATS
486              
487             PPI doesn't currently detect anonymous subroutines, so we don't check
488             those. This should just work when PPI gains that feature.
489              
490             We don't check for C<@ARG>, the alias for C<@_> from English.pm. That's
491             deprecated anyway.
492              
493             =head1 CREDITS
494              
495             Initial development of this policy was supported by a grant from the
496             Perl Foundation.
497              
498             =head1 AUTHOR
499              
500             Chris Dolan <cdolan@cpan.org>
501              
502             =head1 COPYRIGHT
503              
504             Copyright (c) 2007-2023 Chris Dolan
505              
506             This program is free software; you can redistribute it and/or modify
507             it under the same terms as Perl itself. The full text of this license
508             can be found in the LICENSE file included with this module
509              
510             =cut
511              
512             # Local Variables:
513             # mode: cperl
514             # cperl-indent-level: 4
515             # fill-column: 78
516             # indent-tabs-mode: nil
517             # c-indentation-style: bsd
518             # End:
519             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :