File Coverage

blib/lib/Perl/Critic/Policy/ValuesAndExpressions/ProhibitComplexVersion.pm
Criterion Covered Total %
statement 69 70 98.5
branch 30 34 88.2
condition 10 12 83.3
subroutine 17 17 100.0
pod 4 5 80.0
total 130 138 94.2


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::ValuesAndExpressions::ProhibitComplexVersion;
2              
3 40     40   28323 use 5.010001;
  40         163  
4 40     40   257 use strict;
  40         101  
  40         1072  
5 40     40   238 use warnings;
  40         104  
  40         1009  
6              
7 40     40   238 use Carp;
  40         103  
  40         3882  
8 40     40   1484 use English qw(-no_match_vars);
  40         107  
  40         1392  
9 40     40   15447 use Perl::Critic::Utils qw{ :booleans :characters :severities };
  40         110  
  40         2041  
10 40         2558 use Perl::Critic::Utils::PPI qw{
11             get_next_element_in_same_simple_statement
12             get_previous_module_used_on_same_line
13             is_ppi_simple_statement
14 40     40   12833 };
  40         110  
15 40     40   278 use Readonly;
  40         153  
  40         1992  
16              
17 40     40   264 use parent 'Perl::Critic::Policy';
  40         95  
  40         243  
18              
19             our $VERSION = '1.148';
20              
21             #-----------------------------------------------------------------------------
22              
23             Readonly::Scalar my $DOLLAR => q<$>;
24             # All uses of the $DOLLAR variable below are to prevent false failures in
25             # xt/93_version.t.
26             Readonly::Scalar my $VERSION_MODULE => q<version>;
27             Readonly::Scalar my $VERSION_VARIABLE => $DOLLAR . q<VERSION>;
28              
29             Readonly::Scalar my $DESC =>
30             $DOLLAR . q<VERSION value should not come from outside module>;
31             Readonly::Scalar my $EXPL =>
32             q<If the version comes from outside the module, you can get everything from unexpected version changes to denial-of-service attacks.>;
33              
34             #-----------------------------------------------------------------------------
35              
36             sub supported_parameters { return (
37             {
38 95     95 0 2211 name => 'forbid_use_version',
39             description =>
40             qq<Make "use version; our ${DOLLAR}VERSION = qv('1.2.3');" a violation of this policy.>,
41             default_string => $FALSE,
42             behavior => 'boolean',
43             },
44             );
45             }
46 90     90 1 395 sub default_severity { return $SEVERITY_MEDIUM }
47 74     74 1 302 sub default_themes { return qw( core maintenance ) }
48 35     35 1 143 sub applies_to { return 'PPI::Token::Symbol' }
49              
50             #-----------------------------------------------------------------------------
51              
52             sub violates {
53 365     365 1 917 my ( $self, $elem, $doc ) = @_;
54              
55             # Any variable other than $VERSION is ignored.
56 365 100       1036 return if $VERSION_VARIABLE ne $elem->content();
57              
58             # We are only interested in assignments to $VERSION, but it might be a
59             # list assignment, so if we do not find an assignment, we move up the
60             # parse tree. If we hit a statement (or no parent at all) we do not
61             # understand the code to be an assignment statement, and we simply return.
62 197         1152 my $operator;
63             return if
64 197 100 66     640 not $operator = get_next_element_in_same_simple_statement( $elem )
65             or $EQUAL ne $operator;
66              
67             # Find the simple statement we are in. If we can not find it, abandon the
68             # attempt to analyze the code.
69 194 50       10249 my $statement = $self->_get_simple_statement( $elem )
70             or return;
71              
72             # Check all symbols in the statement for violation.
73 194         2458 my $exception;
74 194 100       548 return $exception if
75             $exception =
76             $self->_validate_fully_qualified_symbols($elem, $statement, $doc);
77              
78             # At this point we have found no data that is explicitly from outside the
79             # file. If the author wants to use a $VERSION from another module, _and_
80             # wants MM->parse_version to understand it, the other module must be used
81             # on the same line. So we assume no violation unless this has been done.
82 180 100       656 my $module = get_previous_module_used_on_same_line( $elem )
83             or return;
84              
85             # We make an exception for 'use version' unless configured otherwise; so
86             # let it be written, so let it be done.
87 9 100 100     186 return if $module eq $VERSION_MODULE and not $self->{_forbid_use_version};
88              
89             # We assume nefarious intent if we have any other module used on the same
90             # line as the $VERSION assignment.
91 2         47 return $self->violation( $DESC, $EXPL, $elem );
92             }
93              
94             #-----------------------------------------------------------------------------
95              
96             # Return the simple statement that contains our element. The classification
97             # done by is_ppi_simple_statement is not quite good enough in this case -- if
98             # our parent is a PPI::Structure::List, we want to keep looking.
99              
100             sub _get_simple_statement {
101 194     194   514 my ( $self, $elem ) = @_;
102              
103 194         439 my $statement = $elem;
104              
105 194         642 while ( $statement) {
106 440         1794 my $parent;
107 440 100       1030 if ( is_ppi_simple_statement( $statement ) ) {
108 197 100 66     1546 return $statement if
109             not $parent = $statement->parent()
110             or not $parent->isa( 'PPI::Structure::List' );
111 3         47 $statement = $parent;
112             } else {
113 243         1999 $statement = $statement->parent();
114             }
115             }
116              
117 0         0 return;
118             }
119              
120             #-----------------------------------------------------------------------------
121              
122             sub _validate_fully_qualified_symbols {
123 194     194   457 my ( $self, $elem, $statement, $doc ) = @_;
124              
125             # Find the package(s) in this file.
126             my %local_package =
127 27         152 map { $_->schild( 1 ) => 1 }
128 194 100       371 @{ $doc->find( 'PPI::Statement::Package' ) || [] };
  194         700  
129 194         1260 $local_package{main} = 1; # For completeness.
130              
131             # Check all symbols in the statement for violation.
132 194         375 foreach my $symbol (
133 194 50       596 @{ $statement->find( 'PPI::Token::Symbol' ) || [] }
134             ) {
135 225 100       107244 if ( $symbol->canonical() =~ m< \A [@\$%&] ([\w:]*) :: >smx ) {
136 10 100       221 $local_package{ $1 }
137             or return $self->violation( $DESC, $EXPL, $elem );
138             }
139             }
140              
141             # Check all interpolatable strings in the statement for violation.
142             # TODO this does not correctly handle "@{[some_expression()]}".
143 185         2866 foreach my $string (
144             @{
145             $statement->find(
146             sub {
147             return
148 2882   100 2882   40965 $_[1]->isa('PPI::Token::Quote::Double')
149             || $_[1]->isa('PPI::Token::Quote::Interpolate');
150             }
151             )
152 185 100       1055 or []
153             }
154             ) {
155 72         977 my $unquoted = $string->string();
156 72         743 while (
157             $unquoted =~
158             m<
159             (?: \A | [^\\] )
160             (?: \\{2} )*
161             [@\$]
162             [{]?
163             ([\w:]*)
164             ::
165             >gsmx
166             ) {
167 1 50       7 next if $local_package{ $1 };
168              
169 1         7 return $self->violation( $DESC, $EXPL, $elem );
170             }
171             }
172              
173             # Check all words in the statement for violation.
174 184 100       2407 foreach my $symbol ( @{ $statement->find( 'PPI::Token::Word' ) || [] } ) {
  184         524  
175 275 100       96486 if ( $symbol->content() =~ m/ \A ([\w:]*) :: /smx ) {
176             return $self->violation( $DESC, $EXPL, $elem )
177 4 50       57 if not $local_package{ $1 };
178             }
179             }
180              
181 180         2500 return;
182             }
183              
184             1;
185              
186             __END__
187              
188             #-----------------------------------------------------------------------------
189              
190             =pod
191              
192             =head1 NAME
193              
194             Perl::Critic::Policy::ValuesAndExpressions::ProhibitComplexVersion - Prohibit version values from outside the module.
195              
196             =head1 AFFILIATION
197              
198             This Policy is part of the core L<Perl::Critic|Perl::Critic>
199             distribution.
200              
201              
202             =head1 DESCRIPTION
203              
204             One tempting way to keep a group of related modules at the same version number
205             is to have all of them import the version number from a designated module. For
206             example, module C<Foo::Master> could be the version master for the C<Foo>
207             package, and all other modules could use its C<$VERSION> by
208              
209             use Foo::Master; our $VERSION = $Foo::Master::VERSION;
210              
211             This turns out not to be a good idea, because all sorts of unintended things
212             can happen - anything from unintended version number changes to
213             denial-of-service attacks (since C<Foo::Master> is executed by the 'use').
214              
215             This policy examines statements that assign to C<$VERSION>, and declares a
216             violation under two circumstances: first, if that statement uses a
217             fully-qualified symbol that did not originate in a package declared in the
218             file; second if there is a C<use> statement on the same line that makes the
219             assignment.
220              
221             By default, an exception is made for C<use version;> because of its
222             recommendation by Perl Best Practices. See the C<forbid_use_version>
223             configuration variable if you do not want an exception made for C<use
224             version;>.
225              
226              
227             =head1 CONFIGURATION
228              
229             The construction
230              
231             use version; our $VERSION = qv('1.2.3');
232              
233             is exempt from this policy by default, because it is recommended by Perl Best
234             Practices. Should you wish to identify C<use version;> as a violation, add the
235             following to your perlcriticrc file:
236              
237             [ValuesAndExpressions::ProhibitComplexVersion]
238             forbid_use_version = 1
239              
240              
241             =head1 CAVEATS
242              
243             This code assumes that the hallmark of a violation is a 'use' on the same line
244             as the C<$VERSION> assignment, because that is the way to have it seen by
245             L<ExtUtils::MakeMaker|ExtUtils::MakeMaker>->parse_version(). Other ways to get
246             a version value from outside the module can be imagined, and this policy is
247             currently oblivious to them.
248              
249              
250             =head1 AUTHOR
251              
252             Thomas R. Wyant, III F<wyant at cpan dot org>
253              
254              
255             =head1 COPYRIGHT
256              
257             Copyright (c) 2009-2011 Tom Wyant.
258              
259             This program is free software; you can redistribute it and/or modify
260             it under the same terms as Perl itself. The full text of this license
261             can be found in the LICENSE file included with this module.
262              
263             =cut
264              
265             # Local Variables:
266             # mode: cperl
267             # cperl-indent-level: 4
268             # fill-column: 78
269             # indent-tabs-mode: nil
270             # c-indentation-style: bsd
271             # End:
272             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :