File Coverage

blib/lib/Perl/Critic/Policy/Subroutines/RequireFinalReturn.pm
Criterion Covered Total %
statement 63 123 51.2
branch 15 68 22.0
condition 7 47 14.8
subroutine 21 29 72.4
pod 4 5 80.0
total 110 272 40.4


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Subroutines::RequireFinalReturn;
2              
3 40     40   27375 use 5.010001;
  40         159  
4 40     40   242 use strict;
  40         113  
  40         799  
5 40     40   231 use warnings;
  40         89  
  40         1029  
6 40     40   274 use Readonly;
  40         131  
  40         2176  
7              
8 40     40   286 use List::SomeUtils qw(any);
  40         114  
  40         2151  
9 40     40   295 use Perl::Critic::Exception::Fatal::Internal qw{ throw_internal };
  40         114  
  40         2701  
10 40     40   272 use Perl::Critic::Utils qw{ :characters :severities :data_conversion };
  40         156  
  40         2109  
11 40     40   13239 use parent 'Perl::Critic::Policy';
  40         113  
  40         261  
12              
13             our $VERSION = '1.150';
14              
15             #-----------------------------------------------------------------------------
16              
17             Readonly::Scalar my $EXPL => [ 197 ];
18              
19             Readonly::Hash my %CONDITIONALS => hashify( qw(if unless for foreach) );
20              
21             #-----------------------------------------------------------------------------
22              
23             sub supported_parameters {
24             return (
25             {
26 91     91 0 2586 name => 'terminal_funcs',
27             description => 'The additional subroutines to treat as terminal.',
28             default_string => $EMPTY,
29             behavior => 'string list',
30             list_always_present_values =>
31             [ qw< croak confess die exec exit throw Carp::confess Carp::croak ...> ],
32             },
33             {
34             name => 'terminal_methods',
35             description => 'The additional methods to treat as terminal.',
36             default_string => $EMPTY,
37             behavior => 'string list',
38             list_always_present_values => [],
39             },
40             );
41             }
42              
43 75     75 1 329 sub default_severity { return $SEVERITY_HIGH }
44 92     92 1 368 sub default_themes { return qw( core bugs pbp certrec ) }
45 32     32 1 120 sub applies_to { return 'PPI::Statement::Sub' }
46              
47             #-----------------------------------------------------------------------------
48              
49             sub violates {
50 5     5 1 21 my ( $self, $elem, undef ) = @_;
51              
52             # skip BEGIN{} and INIT{} and END{} etc
53 5 50       37 return if $elem->isa('PPI::Statement::Scheduled');
54              
55 5         16 my @blocks = grep {$_->isa('PPI::Structure::Block')} $elem->schildren();
  15         116  
56 5 50       30 if (@blocks > 1) {
    50          
57             # sanity check
58 0         0 throw_internal 'Subroutine should have no more than one block';
59             }
60             elsif (@blocks == 0) {
61             #Technically, subroutines don't have to have a block at all. In
62             # that case, its just a declaration so this policy doesn't really apply
63 0         0 return; # ok!
64             }
65              
66              
67 5         12 my ($block) = @blocks;
68 5 100 66     16 if ($self->_block_is_empty($block) || $self->_block_has_return($block)) {
69 4         46 return; # OK
70             }
71              
72             # Must be a violation
73 1         3 my $desc;
74 1 50       14 if ( my $name = $elem->name() ) {
75 1         77 $desc = qq<Subroutine "$name" does not end with "return">;
76             }
77             else {
78 0         0 $desc = q<Subroutine does not end with "return">;
79             }
80              
81 1         8 return $self->violation( $desc, $EXPL, $elem );
82             }
83              
84             #-----------------------------------------------------------------------------
85              
86             sub _block_is_empty {
87 5     5   24 my ( $self, $block ) = @_;
88 5         16 return $block->schildren() == 0;
89             }
90              
91             #-----------------------------------------------------------------------------
92              
93             sub _block_has_return {
94 5     5   125 my ( $self, $block ) = @_;
95 5         22 my @blockparts = $block->schildren();
96 5         69 my $final = $blockparts[-1]; # always defined because we call _block_is_empty first
97 5 50       20 return if !$final;
98 5   66     26 return $self->_is_explicit_return($final)
99             || $self->_is_given_when_return($final)
100             || $self->_is_compound_return($final);
101             }
102              
103             #-----------------------------------------------------------------------------
104              
105             sub _is_explicit_return {
106 5     5   15 my ( $self, $final ) = @_;
107              
108 5 100       27 return if $self->_is_conditional_stmnt( $final );
109 4   33     59 return $self->_is_return_or_goto_stmnt( $final )
110             || $self->_is_terminal_stmnt( $final );
111             }
112              
113             #-----------------------------------------------------------------------------
114              
115             sub _is_compound_return {
116 1     1   7 my ( $self, $final ) = @_;
117              
118 1 50       6 if (!$final->isa('PPI::Statement::Compound')) {
119 1         5 return; #fail
120             }
121              
122 0         0 my $begin = $final->schild(0);
123 0 0       0 return if !$begin; #fail
124              
125 0         0 state $is_if_or_unless = { hashify( qw( if unless ) ) };
126 0 0 0     0 if (!($begin->isa('PPI::Token::Word') && $is_if_or_unless->{$begin->content()})) {
127 0         0 return; #fail
128             }
129              
130 0   0     0 my @blocks = grep {!$_->isa('PPI::Structure::Condition') &&
  0         0  
131             !$_->isa('PPI::Token')} $final->schildren();
132             # Sanity check:
133 0 0   0   0 if (any { !$_->isa('PPI::Structure::Block') } @blocks) {
  0         0  
134 0         0 throw_internal
135             'Expected only conditions, blocks and tokens in the if statement';
136             }
137              
138 0 0   0   0 return if any { ! $self->_block_has_return($_) } @blocks;
  0         0  
139              
140 0         0 return 1;
141             }
142              
143             #-----------------------------------------------------------------------------
144              
145             sub _is_given_when_return {
146 1     1   49 my ( $self, $final ) = @_;
147              
148 1 50       10 if ( ! $final->isa( 'PPI::Statement::Given' ) ) {
149 1         11 return; #fail
150             }
151              
152 0         0 my $begin = $final->schild(0);
153 0 0       0 return if !$begin; #fail
154 0 0 0     0 if ( ! ( $begin->isa( 'PPI::Token::Word' ) &&
155             $begin->content() eq 'given' ) ) {
156 0         0 return; #fail
157             }
158              
159 0   0     0 my @blocks = grep {!$_->isa( 'PPI::Structure::Given' ) &&
  0         0  
160             !$_->isa( 'PPI::Token' )} $final->schildren();
161             # Sanity check:
162 0 0   0   0 if (any { !$_->isa('PPI::Structure::Block') } @blocks) {
  0         0  
163 0         0 throw_internal
164             'Expected only givens, blocks and tokens in the given statement';
165             }
166 0 0       0 if (@blocks > 1) {
167             # sanity check
168 0         0 throw_internal 'Given statement should have no more than one block';
169             }
170 0 0       0 @blocks or return; #fail
171              
172 0         0 my $have_default; # We have to fail unless a default block is present
173              
174 0         0 foreach my $stmnt ( $blocks[0]->schildren() ) {
175              
176 0 0       0 if ( $stmnt->isa( 'PPI::Statement::When' ) ) {
177              
178             # Check for the default block.
179 0         0 my $first_token;
180 0 0 0     0 $first_token = $stmnt->schild( 0 )
181             and 'default' eq $first_token->content()
182             and $have_default = 1;
183              
184 0 0       0 $self->_is_when_stmnt_with_return( $stmnt )
185             or return; #fail
186              
187             } else {
188              
189 0 0       0 $self->_is_suffix_when_with_return( $stmnt )
190             or return; #fail
191              
192             }
193              
194             }
195              
196 0         0 return $have_default;
197             }
198              
199             #-----------------------------------------------------------------------------
200              
201             sub _is_return_or_goto_stmnt {
202 4     4   11 my ( $self, $stmnt ) = @_;
203 4 50       16 return if not $stmnt->isa('PPI::Statement::Break');
204 4   50     43 my $first_token = $stmnt->schild(0) || return;
205 4   33     55 return $first_token->content() eq 'return'
206             || $first_token->content() eq 'goto';
207             }
208              
209             #-----------------------------------------------------------------------------
210              
211             sub _is_terminal_stmnt {
212 0     0   0 my ( $self, $stmnt ) = @_;
213              
214 0 0       0 return if not $stmnt->isa('PPI::Statement');
215              
216 0   0     0 my $first_token = $stmnt->schild(0) || return;
217 0 0       0 return 1 if exists $self->{_terminal_funcs}->{$first_token};
218              
219 0   0     0 my $second_token = $stmnt->schild(1) || return;
220 0 0 0     0 return if not ( $second_token->isa('PPI::Token::Operator') && ($second_token eq q{->}) );
221              
222 0   0     0 my $third_token = $stmnt->schild(2) || return;
223 0         0 return exists $self->{_terminal_methods}->{$third_token};
224             }
225              
226             #-----------------------------------------------------------------------------
227              
228             sub _is_conditional_stmnt {
229 5     5   13 my ( $self, $stmnt ) = @_;
230 5 50       22 return if not $stmnt->isa('PPI::Statement');
231              
232 5 100   15   30 return any { exists $CONDITIONALS{$_} && $_->isa('PPI::Token::Word') } $stmnt->schildren();
  15         201  
233             }
234              
235             #-----------------------------------------------------------------------------
236              
237             sub _is_when_stmnt_with_return {
238 0     0     my ( $self, $stmnt ) = @_;
239              
240 0   0       my @inner = grep { ! $_->isa( 'PPI::Token' ) &&
  0            
241             ! $_->isa( 'PPI::Structure::When' ) }
242             $stmnt->schildren();
243 0 0   0     if ( any { ! $_->isa( 'PPI::Structure::Block' ) } @inner ) {
  0            
244 0           throw_internal 'When statement should contain only tokens, conditions, and blocks';
245             }
246 0 0         @inner > 1
247             and throw_internal 'When statement should have no more than one block';
248 0 0         @inner or return; #fail
249              
250 0 0   0     return if any { ! $self->_block_has_return( $_ ) } @inner;
  0            
251              
252 0           return 1; #succeed
253             }
254              
255             #-----------------------------------------------------------------------------
256              
257             sub _is_suffix_when_with_return {
258 0     0     my ( $self, $stmnt ) = @_;
259 0 0         return if not $stmnt->isa('PPI::Statement');
260 0           foreach my $elem ( $stmnt->schildren() ) {
261 0 0 0       return ( $self->_is_return_or_goto_stmnt( $stmnt ) ||
      0        
262             $self->_is_terminal_stmnt( $stmnt ) )
263             if $elem->isa( 'PPI::Token::Word' )
264             && 'when' eq $elem->content();
265             }
266 0           return;
267             }
268              
269             1;
270              
271             __END__
272              
273             #-----------------------------------------------------------------------------
274              
275             =pod
276              
277             =head1 NAME
278              
279             Perl::Critic::Policy::Subroutines::RequireFinalReturn - End every path through a subroutine with an explicit C<return> statement.
280              
281             =head1 AFFILIATION
282              
283             This Policy is part of the core L<Perl::Critic|Perl::Critic>
284             distribution.
285              
286              
287             =head1 DESCRIPTION
288              
289             Require all subroutines to terminate explicitly with one of the
290             following: C<return>, C<carp>, C<croak>, C<die>, C<exec>, C<exit>,
291             C<goto>, or C<throw>.
292              
293             Subroutines without explicit return statements at their ends can be
294             confusing. It can be challenging to deduce what the return value will
295             be.
296              
297             Furthermore, if the programmer did not mean for there to be a
298             significant return value, and omits a return statement, some of the
299             subroutine's inner data can leak to the outside. Consider this case:
300              
301             package Password;
302             # every time the user guesses the password wrong, its value
303             # is rotated by one character
304             my $password;
305             sub set_password {
306             $password = shift;
307             }
308             sub check_password {
309             my $guess = shift;
310             if ($guess eq $password) {
311             unlock_secrets();
312             } else {
313             $password = (substr $password, 1).(substr $password, 0, 1);
314             }
315             }
316             1;
317              
318             In this case, the last statement in check_password() is the
319             assignment. The result of that assignment is the implicit return
320             value, so a wrong guess returns the right password! Adding a
321             C<return;> at the end of that subroutine solves the problem.
322              
323             The only exception allowed is an empty subroutine.
324              
325             Be careful when fixing problems identified by this Policy; don't
326             blindly put a C<return;> statement at the end of every subroutine.
327              
328             =head1 CONFIGURATION
329              
330             If you've created your own terminal functions that behave like C<die>
331             or C<exit>, then you can configure Perl::Critic to recognize those
332             functions as well. Just put something like this in your
333             F<.perlcriticrc>:
334              
335             [Subroutines::RequireFinalReturn]
336             terminal_funcs = quit abort bailout
337              
338             If you've created your own terminal methods, then you can configure
339             Perl::Critic to recognize those methods as well, but the class won't
340             be considered. For example if you define throw_exception as terminal,
341             then any method of that name will be terminal, regardless of class.
342             Just put something like this in your
343             F<.perlcriticrc>:
344              
345             [Subroutines::RequireFinalReturn]
346             terminal_methods = throw_exception
347              
348             =head1 BUGS
349              
350             We do not look for returns inside ternary operators. That
351             construction is too complicated to analyze right now. Besides, a
352             better form is the return outside of the ternary like this: C<return
353             foo ? 1 : bar ? 2 : 3>
354              
355             =head1 AUTHOR
356              
357             Chris Dolan <cdolan@cpan.org>
358              
359             =head1 COPYRIGHT
360              
361             Copyright (c) 2005-2023 Chris Dolan.
362              
363             This program is free software; you can redistribute it and/or modify
364             it under the same terms as Perl itself. The full text of this license
365             can be found in the LICENSE file included with this module.
366              
367             =cut
368              
369             ##############################################################################
370             # Local Variables:
371             # mode: cperl
372             # cperl-indent-level: 4
373             # fill-column: 78
374             # indent-tabs-mode: nil
375             # c-indentation-style: bsd
376             # End:
377             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :