Filename | /Users/timbo/perl5/perlbrew/perls/perl-5.18.2/lib/site_perl/5.18.2/Perl/Critic/Policy/RegularExpressions/ProhibitCaptureWithoutTest.pm |
Statements | Executed 20 statements in 1.15ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 16µs | 16µs | BEGIN@10 | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
1 | 1 | 1 | 10µs | 31µs | BEGIN@13 | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
1 | 1 | 1 | 8µs | 11µs | BEGIN@12 | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
1 | 1 | 1 | 7µs | 60µs | BEGIN@16 | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
1 | 1 | 1 | 7µs | 234µs | BEGIN@15 | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
1 | 1 | 1 | 7µs | 18µs | BEGIN@11 | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
1 | 1 | 1 | 6µs | 6µs | supported_parameters | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
1 | 1 | 1 | 6µs | 7µs | default_severity | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | __ANON__[:179] | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | _find_exposed_match_or_substitute | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | _get_method_name | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | _is_control_transfer_to_left | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | _is_control_transfer_to_right | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | _is_in_conditional_expression | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | _is_in_conditional_structure | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | _unambiguous_control_transfer | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | _unambiguous_goto | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | applies_to | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | default_themes | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
0 | 0 | 0 | 0s | 0s | violates | Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | ############################################################################## | ||||
2 | # $URL$ | ||||
3 | # $Date$ | ||||
4 | # $Author$ | ||||
5 | # $Revision$ | ||||
6 | ############################################################################## | ||||
7 | |||||
8 | package Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest; | ||||
9 | |||||
10 | 2 | 43µs | 1 | 16µs | # spent 16µs within Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@10 which was called:
# once (16µs+0s) by Module::Pluggable::Object::_require at line 10 # spent 16µs making 1 call to Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@10 |
11 | 2 | 20µs | 2 | 28µs | # spent 18µs (7+11) within Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@11 which was called:
# once (7µs+11µs) by Module::Pluggable::Object::_require at line 11 # spent 18µs making 1 call to Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@11
# spent 11µs making 1 call to strict::import |
12 | 2 | 18µs | 2 | 15µs | # spent 11µs (8+4) within Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@12 which was called:
# once (8µs+4µs) by Module::Pluggable::Object::_require at line 12 # spent 11µs making 1 call to Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@12
# spent 4µs making 1 call to warnings::import |
13 | 2 | 23µs | 2 | 51µs | # spent 31µs (10+20) within Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@13 which was called:
# once (10µs+20µs) by Module::Pluggable::Object::_require at line 13 # spent 31µs making 1 call to Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@13
# spent 20µs making 1 call to Exporter::import |
14 | |||||
15 | 2 | 24µs | 2 | 460µs | # spent 234µs (7+226) within Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@15 which was called:
# once (7µs+226µs) by Module::Pluggable::Object::_require at line 15 # spent 234µs making 1 call to Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@15
# spent 226µs making 1 call to Exporter::import |
16 | 2 | 1000µs | 2 | 112µs | # spent 60µs (7+52) within Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@16 which was called:
# once (7µs+52µs) by Module::Pluggable::Object::_require at line 16 # spent 60µs making 1 call to Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::BEGIN@16
# spent 52µs making 1 call to base::import |
17 | |||||
18 | 1 | 600ns | our $VERSION = '1.121'; | ||
19 | |||||
20 | #----------------------------------------------------------------------------- | ||||
21 | |||||
22 | 1 | 4µs | 2 | 36µs | Readonly::Hash my %CONDITIONAL_OPERATOR => hashify( qw{ && || ? and or xor } ); # spent 31µs making 1 call to Readonly::Hash
# spent 4µs making 1 call to Perl::Critic::Utils::hashify |
23 | 1 | 2µs | 2 | 26µs | Readonly::Hash my %UNAMBIGUOUS_CONTROL_TRANSFER => hashify( # spent 23µs making 1 call to Readonly::Hash
# spent 3µs making 1 call to Perl::Critic::Utils::hashify |
24 | qw< next last redo return > ); | ||||
25 | |||||
26 | 1 | 1µs | 1 | 23µs | Readonly::Scalar my $DESC => q{Capture variable used outside conditional}; # spent 23µs making 1 call to Readonly::Scalar |
27 | 1 | 2µs | 1 | 41µs | Readonly::Scalar my $EXPL => [ 253 ]; # spent 41µs making 1 call to Readonly::Scalar |
28 | |||||
29 | #----------------------------------------------------------------------------- | ||||
30 | |||||
31 | # spent 6µs within Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::supported_parameters which was called:
# once (6µs+0s) by Perl::Critic::Policy::new at line 88 of Perl/Critic/Policy.pm | ||||
32 | { | ||||
33 | 1 | 8µs | name => 'exception_source', | ||
34 | description => 'Names of ways to generate exceptions', | ||||
35 | behavior => 'string list', | ||||
36 | list_always_present_values => [ qw{ die croak confess } ], | ||||
37 | } | ||||
38 | ); | ||||
39 | } | ||||
40 | 1 | 2µs | # spent 7µs (6+1) within Perl::Critic::Policy::RegularExpressions::ProhibitCaptureWithoutTest::default_severity which was called:
# once (6µs+1µs) by Perl::Critic::Policy::get_severity at line 331 of Perl/Critic/Policy.pm | ||
41 | sub default_themes { return qw(core pbp maintenance certrule ) } | ||||
42 | sub applies_to { return 'PPI::Token::Magic' } | ||||
43 | |||||
44 | #----------------------------------------------------------------------------- | ||||
45 | |||||
46 | sub violates { | ||||
47 | my ($self, $elem, $doc) = @_; | ||||
48 | # TODO named capture variables | ||||
49 | return if $elem !~ m/\A \$[1-9] \z/xms; | ||||
50 | return if _is_in_conditional_expression($elem); | ||||
51 | return if $self->_is_in_conditional_structure($elem); | ||||
52 | return $self->violation( $DESC, $EXPL, $elem ); | ||||
53 | } | ||||
54 | |||||
55 | sub _is_in_conditional_expression { | ||||
56 | my $elem = shift; | ||||
57 | |||||
58 | # simplistic check: is there a conditional operator between a match and | ||||
59 | # the capture var? | ||||
60 | my $psib = $elem->sprevious_sibling; | ||||
61 | while ($psib) { | ||||
62 | if ($psib->isa('PPI::Token::Operator')) { | ||||
63 | my $op = $psib->content; | ||||
64 | if ( $CONDITIONAL_OPERATOR{ $op } ) { | ||||
65 | $psib = $psib->sprevious_sibling; | ||||
66 | while ($psib) { | ||||
67 | return 1 if ($psib->isa('PPI::Token::Regexp::Match')); | ||||
68 | return 1 if ($psib->isa('PPI::Token::Regexp::Substitute')); | ||||
69 | $psib = $psib->sprevious_sibling; | ||||
70 | } | ||||
71 | return; # false | ||||
72 | } | ||||
73 | } | ||||
74 | $psib = $psib->sprevious_sibling; | ||||
75 | } | ||||
76 | |||||
77 | return; # false | ||||
78 | } | ||||
79 | |||||
80 | sub _is_in_conditional_structure { | ||||
81 | my ( $self, $elem ) = @_; | ||||
82 | |||||
83 | my $stmt = $elem->statement(); | ||||
84 | while ($stmt && $elem->isa('PPI::Statement::Expression')) { | ||||
85 | #return if _is_in_conditional_expression($stmt); | ||||
86 | $stmt = $stmt->statement(); | ||||
87 | } | ||||
88 | return if !$stmt; | ||||
89 | |||||
90 | # Check if any previous statements in the same scope have regexp matches | ||||
91 | my $psib = $stmt->sprevious_sibling; | ||||
92 | while ($psib) { | ||||
93 | if ( $psib->isa( 'PPI::Node' ) and | ||||
94 | my $match = _find_exposed_match_or_substitute( $psib ) ) { | ||||
95 | return _is_control_transfer_to_left( $self, $match, $elem ) || | ||||
96 | _is_control_transfer_to_right( $self, $match, $elem ); | ||||
97 | } | ||||
98 | $psib = $psib->sprevious_sibling; | ||||
99 | } | ||||
100 | |||||
101 | # Check for an enclosing 'if', 'unless', 'elsif', 'else', or 'when' | ||||
102 | my $parent = $stmt->parent; | ||||
103 | while ($parent) { # never false as long as we're inside a PPI::Document | ||||
104 | if ($parent->isa('PPI::Statement::Compound') || | ||||
105 | $parent->isa('PPI::Statement::When' ) | ||||
106 | ) { | ||||
107 | return 1; | ||||
108 | } | ||||
109 | elsif ($parent->isa('PPI::Structure')) { | ||||
110 | return 1 if _is_in_conditional_expression($parent); | ||||
111 | return 1 if $self->_is_in_conditional_structure($parent); | ||||
112 | $parent = $parent->parent; | ||||
113 | } | ||||
114 | else { | ||||
115 | last; | ||||
116 | } | ||||
117 | } | ||||
118 | |||||
119 | return; # fail | ||||
120 | } | ||||
121 | |||||
122 | # This subroutine returns true if there is a control transfer to the left of | ||||
123 | # the match operation which would bypass the capture variable. The arguments | ||||
124 | # are the match operation and the capture variable. | ||||
125 | sub _is_control_transfer_to_left { | ||||
126 | my ( $self, $match, $elem ) = @_; | ||||
127 | # If a regexp match is found, we succeed if a match failure | ||||
128 | # appears to throw an exception, and fail otherwise. RT 36081 | ||||
129 | my $prev = $match->sprevious_sibling() or return; | ||||
130 | while ( not ( $prev->isa( 'PPI::Token::Word' ) && | ||||
131 | q<unless> eq $prev->content() ) ) { | ||||
132 | $prev = $prev->sprevious_sibling() or return; | ||||
133 | } | ||||
134 | # In this case we analyze the first thing to appear in the parent of the | ||||
135 | # 'unless'. This is the simplest case, and it will not be hard to dream up | ||||
136 | # cases where this is insufficient (e.g. do {something(); die} unless ...) | ||||
137 | my $parent = $prev->parent() or return; | ||||
138 | my $first = $parent->schild( 0 ) or return; | ||||
139 | if ( my $method = _get_method_name( $first ) ) { | ||||
140 | # Methods can also be exception sources. | ||||
141 | return $self->{_exception_source}{ $method->content() }; | ||||
142 | } | ||||
143 | return $self->{_exception_source}{ $first->content() } || | ||||
144 | _unambiguous_control_transfer( $first, $elem ); | ||||
145 | } | ||||
146 | |||||
147 | # This subroutine returns true if there is a control transfer to the right of | ||||
148 | # the match operation which would bypass the capture variable. The arguments | ||||
149 | # are the match operation and the capture variable. | ||||
150 | sub _is_control_transfer_to_right { | ||||
151 | my ( $self, $match, $elem ) = @_; | ||||
152 | # If a regexp match is found, we succeed if a match failure | ||||
153 | # appears to throw an exception, and fail otherwise. RT 36081 | ||||
154 | my $oper = $match->snext_sibling() or return; # fail | ||||
155 | my $oper_content = $oper->content(); | ||||
156 | # We do not check 'dor' or '//' because a match failure does not | ||||
157 | # return an undefined value. | ||||
158 | q{or} eq $oper_content | ||||
159 | or q{||} eq $oper_content | ||||
160 | or return; # fail | ||||
161 | my $next = $oper->snext_sibling() or return; # fail | ||||
162 | if ( my $method = _get_method_name( $next ) ) { | ||||
163 | # Methods can also be exception sources. | ||||
164 | return $self->{_exception_source}{ $method->content() }; | ||||
165 | } | ||||
166 | return $self->{_exception_source}{ $next->content() } || | ||||
167 | _unambiguous_control_transfer( $next, $elem ); | ||||
168 | } | ||||
169 | |||||
170 | # Given a PPI::Node, find the last regexp match or substitution that is | ||||
171 | # in-scope to the node's next sibling. | ||||
172 | sub _find_exposed_match_or_substitute { # RT 36081 | ||||
173 | my $elem = shift; | ||||
174 | FIND_REGEXP_NOT_IN_BLOCK: | ||||
175 | foreach my $regexp ( reverse @{ $elem->find( | ||||
176 | sub { | ||||
177 | return $_[1]->isa( 'PPI::Token::Regexp::Substitute' ) | ||||
178 | || $_[1]->isa( 'PPI::Token::Regexp::Match' ); | ||||
179 | } | ||||
180 | ) || [] } ) { | ||||
181 | my $parent = $regexp->parent(); | ||||
182 | while ( $parent != $elem ) { | ||||
183 | $parent->isa( 'PPI::Structure::Block' ) | ||||
184 | and next FIND_REGEXP_NOT_IN_BLOCK; | ||||
185 | $parent = $parent->parent() | ||||
186 | or next FIND_REGEXP_NOT_IN_BLOCK; | ||||
187 | } | ||||
188 | return $regexp; | ||||
189 | } | ||||
190 | return; | ||||
191 | } | ||||
192 | |||||
193 | # If the argument introduces a method call, return the method name; | ||||
194 | # otherwise just return. | ||||
195 | sub _get_method_name { | ||||
196 | my ( $elem ) = @_; | ||||
197 | # We fail unless the element we were given looks like it might be an | ||||
198 | # object or a class name. | ||||
199 | $elem or return; | ||||
200 | ( | ||||
201 | $elem->isa( 'PPI::Token::Symbol' ) && | ||||
202 | q<$> eq $elem->raw_type() || | ||||
203 | $elem->isa( 'PPI::Token::Word' ) && | ||||
204 | $elem->content() =~ m/ \A [\w:]+ \z /smx | ||||
205 | ) or return; | ||||
206 | # We skip over all the subscripts and '->' operators to the right of | ||||
207 | # the original element, failing if we run out of objects. | ||||
208 | my $prior; | ||||
209 | my $next = $elem->snext_sibling() or return; | ||||
210 | while ( $next->isa( 'PPI::Token::Subscript' ) || | ||||
211 | $next->isa( 'PPI::Token::Operator' ) && | ||||
212 | q{->} eq $next->content() ) { | ||||
213 | $prior = $next; | ||||
214 | $next = $next->snext_sibling or return; # fail | ||||
215 | } | ||||
216 | # A method call must have a '->' operator before it. | ||||
217 | ( $prior && | ||||
218 | $prior->isa( 'PPI::Token::Operator' ) && | ||||
219 | q{->} eq $prior->content() | ||||
220 | ) or return; | ||||
221 | # Anything other than a PPI::Token::Word can not be statically | ||||
222 | # recognized as a method name. | ||||
223 | $next->isa( 'PPI::Token::Word' ) or return; | ||||
224 | # Whatever we have left at this point looks very like a method name. | ||||
225 | return $next; | ||||
226 | } | ||||
227 | |||||
228 | # Determine whether the given element represents an unambiguous transfer of | ||||
229 | # control around anything that follows it in the same block. The arguments are | ||||
230 | # the element to check, and the capture variable that is the subject of this | ||||
231 | # call to the policy. | ||||
232 | sub _unambiguous_control_transfer { # RT 36081. | ||||
233 | my ( $xfer, $elem ) = @_; | ||||
234 | |||||
235 | my $content = $xfer->content(); | ||||
236 | |||||
237 | # Anything in the hash is always a transfer of control. | ||||
238 | return $TRUE if $UNAMBIGUOUS_CONTROL_TRANSFER{ $content }; | ||||
239 | |||||
240 | # A goto is not unambiguous on the face of it, but at least some forms of | ||||
241 | # it can be accepted. | ||||
242 | q<goto> eq $content | ||||
243 | and return _unambiguous_goto( $xfer, $elem ); | ||||
244 | |||||
245 | # Anything left at this point is _not_ an unambiguous transfer of control | ||||
246 | # around whatever follows it. | ||||
247 | return; | ||||
248 | } | ||||
249 | |||||
250 | # Determine whether the given goto represents an unambiguous transfer of | ||||
251 | # control around anything that follows it in the same block. The arguments are | ||||
252 | # the element to check, and the capture variable that is the subject of this | ||||
253 | # call to the policy. | ||||
254 | sub _unambiguous_goto { | ||||
255 | my ( $xfer, $elem ) = @_; | ||||
256 | |||||
257 | # A goto without a target? | ||||
258 | my $target = $xfer->snext_sibling() or return; | ||||
259 | |||||
260 | # The co-routine form of goto is an unambiguous transfer of control. | ||||
261 | $target->isa( 'PPI::Token::Symbol' ) | ||||
262 | and q<&> eq $target->raw_type() | ||||
263 | and return $TRUE; | ||||
264 | |||||
265 | # The label form of goto is an unambiguous transfer of control, | ||||
266 | # provided the label does not occur between the goto and the capture | ||||
267 | # variable. | ||||
268 | if ( $target->isa( 'PPI::Token::Word' ) ) { | ||||
269 | |||||
270 | # We need to search in our most-local block, or the document if | ||||
271 | # there is no enclosing block. | ||||
272 | my $container = $target; | ||||
273 | while ( my $parent = $container->parent() ) { | ||||
274 | $container = $parent; | ||||
275 | $container->isa( 'PPI::Structure::Block' ) and last; | ||||
276 | } | ||||
277 | |||||
278 | # We search the container for our label. If we find it, we return | ||||
279 | # true if it occurs before the goto or after the capture variable, | ||||
280 | # otherwise we return false. If we do not find it we return true. | ||||
281 | # Note that perl does not seem to consider duplicate labels an | ||||
282 | # error, but also seems to take the first one in the relevant | ||||
283 | # scope when this happens. | ||||
284 | my $looking_for = qr/ \A @{[ $target->content() ]} \s* : \z /smx; | ||||
285 | my ($start_line, $start_char) = @{ $xfer->location() || [] }; | ||||
286 | defined $start_line or return; # document not indexed. | ||||
287 | my ($end_line, $end_char) = @{ $elem->location() || [] }; | ||||
288 | foreach my $label ( | ||||
289 | @{ $container->find( 'PPI::Token::Label' ) || [] } ) | ||||
290 | { | ||||
291 | $label->content() =~ m/$looking_for/smx or next; | ||||
292 | my ( $line, $char ) = @{ $label->location() || [] }; | ||||
293 | return $TRUE | ||||
294 | if $line < $start_line || | ||||
295 | $line == $start_line && $char < $start_char; | ||||
296 | return $TRUE | ||||
297 | if $line > $end_line || | ||||
298 | $line == $end_line && $char > $end_char; | ||||
299 | return; | ||||
300 | } | ||||
301 | return $TRUE; | ||||
302 | } | ||||
303 | |||||
304 | # Any other form of goto can not be statically analyzed, and so is not | ||||
305 | # an unambiguous transfer of control around the capture variable. | ||||
306 | return; | ||||
307 | } | ||||
308 | |||||
309 | 1 | 6µs | 1; | ||
310 | |||||
311 | #----------------------------------------------------------------------------- | ||||
312 | |||||
313 | __END__ |