File Coverage

blib/lib/Perl/Critic/Policy/Variables/ProhibitEvilVariables.pm
Criterion Covered Total %
statement 35 102 34.3
branch 2 34 5.8
condition 1 16 6.2
subroutine 15 22 68.1
pod 5 6 83.3
total 58 180 32.2


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Variables::ProhibitEvilVariables;
2              
3 40     40   28549 use 5.010001;
  40         194  
4 40     40   264 use strict;
  40         136  
  40         860  
5 40     40   219 use warnings;
  40         126  
  40         1168  
6              
7 40     40   250 use English qw(-no_match_vars);
  40         131  
  40         381  
8 40     40   15306 use List::SomeUtils qw(firstval);
  40         148  
  40         1886  
9 40     40   293 use Readonly;
  40         108  
  40         1941  
10              
11             use Perl::Critic::Exception::Configuration::Option::Policy::ParameterValue
12 40     40   328 qw{ throw_policy_value };
  40         104  
  40         2937  
13 40         1989 use Perl::Critic::Utils qw{
14             :characters :severities :data_conversion
15 40     40   298 };
  40         120  
16              
17 40     40   12762 use parent 'Perl::Critic::Policy';
  40         152  
  40         278  
18              
19             our $VERSION = '1.150';
20              
21             #-----------------------------------------------------------------------------
22              
23             Readonly::Scalar my $EXPL => q{Find an alternative variable};
24              
25             Readonly::Hash my %SUBSCRIPTED_TYPE => hashify(qw{@ %});
26              
27             Readonly::Scalar my $VARIABLE_NAME_REGEX => qr< [\$\@%] \S+ >xms;
28             Readonly::Scalar my $REGULAR_EXPRESSION_REGEX =>
29             qr< [/] ( [^/]+ ) [/] >xms;
30             Readonly::Array my @DESCRIPTION_REGEXES =>
31             qr< [{] ( [^}]+ ) [}] >xms,
32             qr{ < ( [^>]+ ) > }xms,
33             qr{ [[] ( [^]]+ ) []] }xms,
34             qr{ [(] ( [^)]+ ) [)] }xms,
35             ;
36             Readonly::Scalar my $DESCRIPTION_REGEX =>
37             qr< @{[join '|', @DESCRIPTION_REGEXES]} >xms;
38              
39             # It's kind of unfortunate that I had to put capturing parentheses in the
40             # component regexes above, because they're not visible here and so make
41             # figuring out the positions of captures hard. Too bad we can't make the
42             # minimum perl version 5.10. :]
43             Readonly::Scalar my $VARIABLES_REGEX =>
44             qr<
45             \A
46             \s*
47             (?:
48             ( $VARIABLE_NAME_REGEX )
49             | $REGULAR_EXPRESSION_REGEX
50             )
51             (?: \s* $DESCRIPTION_REGEX )?
52             \s*
53             >xms;
54              
55             Readonly::Scalar my $VARIABLES_FILE_LINE_REGEX =>
56             qr<
57             \A
58             \s*
59             (?:
60             ( $VARIABLE_NAME_REGEX )
61             | $REGULAR_EXPRESSION_REGEX
62             )
63             \s*
64             ( \S (?: .* \S )? )?
65             \s*
66             \z
67             >xms;
68              
69             # Indexes in the arrays of regexes for the "variables" option.
70             Readonly::Scalar my $INDEX_REGEX => 0;
71             Readonly::Scalar my $INDEX_DESCRIPTION => 1;
72              
73             #-----------------------------------------------------------------------------
74              
75             sub supported_parameters {
76             return (
77             {
78 91     91 0 2481 name => 'variables',
79             description => 'The names of or patterns for variables to forbid.',
80             default_string => $EMPTY,
81             parser => \&_parse_variables,
82             },
83             {
84             name => 'variables_file',
85             description => 'A file containing names of or patterns for variables to forbid.',
86             default_string => $EMPTY,
87             parser => \&_parse_variables_file,
88             },
89             );
90             }
91              
92 84     84 1 407 sub default_severity { return $SEVERITY_HIGHEST }
93 79     79 1 362 sub default_themes { return qw( core bugs ) }
94 0     0 1 0 sub applies_to { return qw{PPI::Token::Symbol} }
95              
96             #-----------------------------------------------------------------------------
97              
98             sub _parse_variables {
99 89     89   381 my ($self, $parameter, $config_string) = @_;
100              
101 89 50       391 return if not $config_string;
102 0 0       0 return if $config_string =~ m< \A \s* \z >xms;
103              
104 0         0 my $variable_specifications = $config_string;
105              
106 0         0 while ( my ($variable, $regex_string, @descrs) =
107             $variable_specifications =~ m< $VARIABLES_REGEX >xms) {
108              
109 0         0 substr $variable_specifications, 0, $LAST_MATCH_END[0], $EMPTY;
110 0     0   0 my $description = firstval { defined } @descrs;
  0         0  
111              
112 0         0 $self->_handle_variable_specification(
113             variable => $variable,
114             regex_string => $regex_string,
115             description => $description,
116             option_name => 'variables',
117             option_value => $config_string,
118             );
119             }
120              
121 0 0       0 if ($variable_specifications) {
122 0         0 throw_policy_value
123             policy => $self->get_short_name(),
124             option_name => 'variables',
125             option_value => $config_string,
126             message_suffix =>
127             qq{contains unparseable data: "$variable_specifications"};
128             }
129              
130 0         0 return;
131             }
132              
133             sub _parse_variables_file {
134 89     89   397 my ($self, $parameter, $config_string) = @_;
135              
136 89 50       351 return if not $config_string;
137 0 0       0 return if $config_string =~ m< \A \s* \z >xms;
138              
139 0 0       0 open my $handle, '<', $config_string
140             or throw_policy_value
141             policy => $self->get_short_name(),
142             option_name => 'variables_file',
143             option_value => $config_string,
144             message_suffix =>
145             qq<refers to a file that could not be opened: $OS_ERROR>;
146 0         0 while ( my $line = <$handle> ) {
147 0         0 $self->_handle_variable_specification_on_line($line, $config_string);
148             }
149 0 0       0 close $handle or warn qq<Could not close "$config_string": $OS_ERROR\n>;
150              
151 0         0 return;
152             }
153              
154             sub _handle_variable_specification_on_line {
155 0     0   0 my ($self, $line, $config_string) = @_;
156              
157 0         0 $line =~ s< [#] .* \z ><>xms;
158 0         0 $line =~ s< \s+ \z ><>xms;
159 0         0 $line =~ s< \A \s+ ><>xms;
160              
161 0 0       0 return if not $line;
162              
163 0 0       0 if ( my ($variable, $regex_string, $description) =
164             $line =~ m< $VARIABLES_FILE_LINE_REGEX >xms) {
165              
166 0         0 $self->_handle_variable_specification(
167             variable => $variable,
168             regex_string => $regex_string,
169             description => $description,
170             option_name => 'variables_file',
171             option_value => $config_string,
172             );
173             }
174             else {
175 0         0 throw_policy_value
176             policy => $self->get_short_name(),
177             option_name => 'variables_file',
178             option_value => $config_string,
179             message_suffix =>
180             qq{contains unparseable data: "$line"};
181             }
182              
183 0         0 return;
184             }
185              
186             sub _handle_variable_specification {
187 0     0   0 my ($self, %arguments) = @_;
188              
189 0   0     0 my $description = $arguments{description} || $EMPTY;
190              
191 0 0       0 if ( my $regex_string = $arguments{regex_string} ) {
192             # These are variable name patterns (e.g. /acme/)
193 0         0 my $actual_regex;
194              
195 0         0 eval { $actual_regex = qr/$regex_string/sm; ## no critic (ExtendedFormatting)
196 0         0 1 }
197             or throw_policy_value
198             policy => $self->get_short_name(),
199             option_name => $arguments{option_name},
200             option_value => $arguments{option_value},
201 0 0       0 message_suffix =>
202             qq{contains an invalid regular expression: "$regex_string"};
203              
204             # Can't use a hash due to stringification, so this is an AoA.
205             push
206 0   0     0 @{ $self->{_evil_variables_regexes} ||= [] },
  0         0  
207             [ $actual_regex, $description ];
208             }
209             else {
210             # These are literal variable names (e.g. $[)
211 0   0     0 $self->{_evil_variables} ||= {};
212 0         0 my $name = $arguments{variable};
213 0         0 $self->{_evil_variables}{$name} = $description;
214             }
215              
216 0         0 return;
217             }
218              
219             #-----------------------------------------------------------------------------
220              
221             sub initialize_if_enabled {
222 74     74 1 316 my ($self, $config) = @_;
223              
224             # Disable if no variables are specified; there's no point in running if
225             # there aren't any.
226             return
227             exists $self->{_evil_variables}
228 74   33     603 || exists $self->{_evil_variables_regexes};
229             }
230              
231             #-----------------------------------------------------------------------------
232              
233             sub violates {
234 0     0 1   my ( $self, $elem, undef ) = @_;
235 0 0         return if not $elem;
236              
237 0 0         my @names = $self->_compute_symbol_names( $elem )
238             or return;
239              
240 0           my $evil_variables = $self->{_evil_variables};
241 0           my $evil_variables_regexes = $self->{_evil_variables_regexes};
242              
243 0           foreach my $variable (@names) {
244             exists $evil_variables->{$variable}
245             and return $self->_make_violation(
246             $variable,
247 0 0         $evil_variables->{$variable},
248             $elem,
249             );
250             }
251              
252 0           foreach my $variable (@names) {
253 0           foreach my $regex ( @{$evil_variables_regexes} ) {
  0            
254 0 0         $variable =~ $regex->[$INDEX_REGEX]
255             and return $self->_make_violation(
256             $variable,
257             $regex->[$INDEX_DESCRIPTION],
258             $elem,
259             );
260             }
261             }
262              
263 0           return; # ok!
264             }
265              
266             #-----------------------------------------------------------------------------
267              
268             # We are unconditionally interested in the names of the symbol itself. If the
269             # symbol is subscripted, we are interested in the subscripted form as well.
270              
271             sub _compute_symbol_names {
272 0     0     my ($self, $elem) = @_;
273              
274 0           my @names;
275              
276 0           my $name = $elem->symbol();
277 0           push @names, $name;
278              
279 0 0         if ($SUBSCRIPTED_TYPE{$elem->symbol_type()}) {
280 0           $name = $elem->content();
281 0           my $next = $elem->snext_sibling();
282 0           my @subscr;
283 0   0       while ($next and $next->isa('PPI::Structure::Subscript')) {
284 0           push @subscr, $next->content();
285 0           $next = $next->snext_sibling();
286             }
287 0 0         if (@subscr) {
288 0           push @names, join $EMPTY, $name, @subscr;
289             }
290             }
291              
292 0           return @names;
293             }
294              
295             #-----------------------------------------------------------------------------
296              
297             sub _make_violation {
298 0     0     my ($self, $variable, $description, $elem) = @_;
299 0   0       return $self->violation(
300             $description || qq<Prohibited variable "$variable" used>,
301             $EXPL,
302             $elem,
303             );
304             }
305              
306             1;
307              
308             __END__
309              
310             #-----------------------------------------------------------------------------
311              
312             =pod
313              
314             =for stopwords subscripted
315              
316             =head1 NAME
317              
318             Perl::Critic::Policy::Variables::ProhibitEvilVariables - Ban variables that aren't blessed by your shop.
319              
320              
321             =head1 AFFILIATION
322              
323             This Policy is part of the core L<Perl::Critic|Perl::Critic>
324             distribution.
325              
326              
327             =head1 DESCRIPTION
328              
329             Use this policy if you wish to prohibit the use of specific variables. These
330             may be global variables warned against in C<perlvar>, or just variables whose
331             names you do not like.
332              
333              
334             =head1 CONFIGURATION
335              
336             The set of prohibited variables is configurable via the C<variables> and
337             C<variables_file> options.
338              
339             The value of C<variables> should be a string of space-delimited, fully
340             qualified variable names and/or regular expressions. An example of
341             prohibiting two specific variables in a F<.perlcriticrc> file:
342              
343             [Variables::ProhibitEvilVariables]
344             variables = $[ $^S $SIG{__DIE__}
345              
346             If you prohibit an array or hash (e.g. C<@INC>), use of elements of the array
347             or hash will be prohibited as well. If you specify a subscripted variable (e.g.
348             C<$SIG{__DIE__}>), only the literal subscript specified will be detected. The
349             above <.perlcritic> file, for example, will cause C<perlcritic (1)> to detect
350             C<$SIG{__DIE__} = \&foo>, but not
351              
352             my $foo = '__DIE__';
353             $SIG{$foo} = \&foo;
354              
355             Regular expressions are identified by values beginning and ending with
356             slashes. Any variable with a name that matches C<m/pattern/sm> will be
357             forbidden. For example:
358              
359             [Variables::ProhibitEvilVariables]
360             variables = /acme/
361              
362             would cause all variables that match C<m/acme/> to be forbidden. If
363             you want a case-blind check, you can use (?i: ... ). For example
364              
365             [Variables::ProhibitEvilVariables]
366             variables = /(?i:acme)/
367              
368             forbids variables that match C<m/acme/smi>.
369              
370             In addition, you can override the default message ("Prohibited variable
371             "I<variable>" used") with your own, in order to give suggestions for
372             alternative action. To do so, put your message in curly braces after
373             the variable name or regular expression. Like this:
374              
375             [Variables::ProhibitEvilVariables]
376             variables = $[ {Found use of $[. Program to base index 0 instead}
377              
378             If your message contains curly braces, you can enclose it in parentheses,
379             angle brackets, or square brackets instead.
380              
381             Similarly, the C<variables_file> option gives the name of a file
382             containing specifications for prohibited variables. Only one variable
383             specification is allowed per line and comments start with an octothorp
384             and run to end of line; no curly braces are necessary for delimiting
385             messages:
386              
387             $[ # Prohibit the "$[" variable and use the default message.
388              
389             # Prohibit the "$^S" variable and give a replacement message.
390             $^S Having to think about $^S in exception handlers is just wrong
391              
392             # Use a regular expression.
393             /acme/ No coyotes allowed.
394              
395             By default, there are no prohibited variables, although I can think of a
396             few that should be. See C<perldoc perlvar> for a few suggestions.
397              
398              
399             =head1 RESTRICTIONS
400              
401             Variables of the form C<${^foo}> are not recognized by PPI as of version
402             1.206. When PPI recognizes these, this policy will Just Work for them too.
403              
404             Only direct references to prohibited variables and literal subscripts will be
405             recognized. For example, if you prohibit $[, the first line in
406              
407             my $foo = \$[;
408             $$foo = 1;
409              
410             will be flagged as a violation, but not the second, even though the second, in
411             fact, assigns to $[. Similarly, if you prohibit $SIG{__DIE__}, this policy
412             will not recognize
413              
414             my $foo = '__DIE__';
415             $SIG{$foo} = sub {warn 'I cannot die!'};
416              
417             as an assignment to $SIG{__DIE__}.
418              
419              
420             =head1 NOTES
421              
422             This policy leans heavily on
423             L<Perl::Critic::Policy::Modules::ProhibitEvilModules|Perl::Critic::Policy::Modules::ProhibitEvilModules>
424             by Jeffrey Ryan Thalhammer.
425              
426              
427             =head1 AUTHOR
428              
429             Thomas R. Wyant, III F<wyant at cpan dot org>
430              
431              
432             =head1 COPYRIGHT
433              
434             Copyright (c) 2009-2011 Thomas R. Wyant, III
435              
436             This program is free software; you can redistribute it and/or modify
437             it under the same terms as Perl itself. The full text of this license
438             can be found in the LICENSE file included with this module.
439              
440             =cut
441              
442             # Local Variables:
443             # mode: cperl
444             # cperl-indent-level: 4
445             # fill-column: 78
446             # indent-tabs-mode: nil
447             # c-indentation-style: bsd
448             # End:
449             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :