File Coverage

blib/lib/Perl/Critic/Policy/InputOutput/RequireBriefOpen.pm
Criterion Covered Total %
statement 106 109 97.2
branch 69 84 82.1
condition 19 23 82.6
subroutine 18 18 100.0
pod 4 5 80.0
total 216 239 90.3


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::InputOutput::RequireBriefOpen;
2              
3 40     40   29604 use 5.010001;
  40         181  
4 40     40   258 use strict;
  40         117  
  40         874  
5 40     40   225 use warnings;
  40         91  
  40         1038  
6              
7 40     40   219 use Readonly;
  40         117  
  40         2199  
8              
9 40     40   305 use List::SomeUtils qw(any);
  40         97  
  40         2307  
10              
11 40         2266 use Perl::Critic::Utils qw{ :severities :classification :booleans
12             hashify parse_arg_list
13 40     40   311 };
  40         120  
14 40     40   16291 use parent 'Perl::Critic::Policy';
  40         145  
  40         291  
15              
16             our $VERSION = '1.146';
17              
18             #-----------------------------------------------------------------------------
19              
20             Readonly::Scalar my $DESC => q<Close filehandles as soon as possible after opening them..>;
21             Readonly::Scalar my $EXPL => [209];
22              
23             Readonly::Scalar my $SCALAR_SIGIL => q<$>;
24             Readonly::Scalar my $GLOB_SIGIL => q<*>;
25              
26             # Identify the builtins that are equivalent to 'open' and 'close'. Note that
27             # 'return' is considered equivalent to 'close'.
28             Readonly::Hash my %CLOSE_BUILTIN => hashify( qw{
29             close
30             CORE::close
31             CORE::GLOBAL::close
32             return
33             } );
34             Readonly::Hash my %OPEN_BUILTIN => hashify( qw{
35             open
36             CORE::open
37             CORE::GLOBAL::open
38             } );
39              
40             # Possible values for $is_lexical
41             Readonly::Scalar my $NOT_LEXICAL => 0; # Guaranteed only false value
42             Readonly::Scalar my $LOCAL_LEXICAL => 1;
43             Readonly::Scalar my $NON_LOCAL_LEXICAL => 2;
44              
45             Readonly::Scalar my $LAST_ELEMENT => -1;
46              
47             #-----------------------------------------------------------------------------
48              
49             sub supported_parameters {
50             return (
51             {
52 122     122 0 2310 name => 'lines',
53             description => 'The maximum number of lines between an open() and a close().',
54             default_string => '9',
55             behavior => 'integer',
56             integer_minimum => 1,
57             },
58             );
59             }
60              
61 93     93 1 433 sub default_severity { return $SEVERITY_HIGH }
62 86     86 1 385 sub default_themes { return qw< core pbp maintenance > }
63 64     64 1 188 sub applies_to { return 'PPI::Token::Word' }
64              
65             #-----------------------------------------------------------------------------
66              
67             sub violates {
68 526     526 1 1180 my ( $self, $elem, undef ) = @_;
69              
70             # Is it a call to open?
71 526 100       1134 $OPEN_BUILTIN{$elem->content()} or return;
72 50 100       679 return if ! is_function_call($elem);
73 49         156 my @open_args = parse_arg_list($elem);
74 49 100       153 return if 2 > @open_args; # not a valid call to open()
75              
76 48         156 my ($is_lexical, $fh) = _get_opened_fh($open_args[0]);
77 48 100       175 return if not $fh;
78 41 100       106 return if $fh =~ m< \A [*]? STD (?: IN|OUT|ERR ) \z >xms;
79              
80 35         244 for my $close_token ( $self->_find_close_invocations_or_return(
81             $elem, $is_lexical ) ) {
82             # The $close_token might be a close() or a return()
83             # It doesn't matter which -- both satisfy this policy
84 27 100 100     96 if (is_function_call($close_token)) {
    100          
85 22         75 my @close_args = parse_arg_list($close_token);
86              
87 22         55 my $close_parameter = $close_args[0];
88 22 100       71 if ('ARRAY' eq ref $close_parameter) {
89 19         37 $close_parameter = ${$close_parameter}[0];
  19         50  
90             }
91 22 100       79 if ( $close_parameter ) {
92 19         58 $close_parameter = "$close_parameter";
93 19 100       105 return if $fh eq $close_parameter;
94              
95 4 50   8   73 if ( any { m< \A [*] >xms } ($fh, $close_parameter) ) {
  8         47  
96 0         0 (my $stripped_fh = $fh) =~ s< \A [*] ><>xms;
97 0         0 (my $stripped_parameter = $close_parameter) =~
98             s< \A [*] ><>xms;
99              
100 0 0       0 return if $stripped_fh eq $stripped_parameter;
101             }
102             }
103             }
104             elsif ($is_lexical && is_method_call($close_token)) {
105 3         51 my $tok = $close_token->sprevious_sibling->sprevious_sibling;
106 3 100       106 return if $fh eq $tok;
107             }
108             }
109              
110 19         145 return $self->violation( $DESC, $EXPL, $elem );
111             }
112              
113             sub _find_close_invocations_or_return {
114 35     35   83 my ($self, $elem, $is_lexical) = @_;
115              
116 35         96 my $parent = $self->_get_scope( $elem, $is_lexical );
117 35 50       118 return if !$parent; # I can't think of a scenario where this would happen
118              
119 35         83 my $open_loc = $elem->location;
120             # we don't actually allow _lines to be zero or undef, but maybe we will
121 35 50       421 my $end_line = $self->{_lines} ? $open_loc->[0] + $self->{_lines} : undef;
122              
123             my $closes = $parent->find(sub {
124             ##no critic (ProhibitExplicitReturnUndef)
125 1642     1642   18405 my ($parent, $candidate) = @_; ## no critic(Variables::ProhibitReusedNames)
126 1642 100       4811 return undef if $candidate->isa('PPI::Statement::Sub');
127 1641         3412 my $candidate_loc = $candidate->location;
128 1641 50       18411 return undef if !defined $candidate_loc->[0];
129 1641 100       3448 return 0 if $candidate_loc->[0] < $open_loc->[0];
130 1290 100 100     3447 return 0 if $candidate_loc->[0] == $open_loc->[0] && $candidate_loc->[1] <= $open_loc->[1];
131 1191 100 66     3520 return undef if defined $end_line && $candidate_loc->[0] > $end_line;
132 1149 100       4576 return 0 if !$candidate->isa('PPI::Token::Word');
133 124   100     324 return $CLOSE_BUILTIN{ $candidate->content() } || 0;
134 35         241 });
135 35 100       563 return @{$closes || []};
  35         195  
136             }
137              
138             sub _get_scope {
139 35     35   90 my ( $self, $elem, $is_lexical ) = @_;
140              
141 35         113 my $open_loc = $elem->location;
142             my $end_line = ( $self->{_lines} && defined $open_loc->[0] ) ?
143             $open_loc->[0] + $self->{_lines} :
144 35 50 33     604 undef;
145              
146 35         119 while ( my $dad = $elem->parent) {
147 78         442 $elem = $dad;
148 78 100       303 next if not $elem->scope;
149              
150             # If we are analyzing something like 'open my $fh ...', the
151             # most-local scope suffices. RT #64437
152 38 100       136 return $elem if $LOCAL_LEXICAL == $is_lexical;
153 13 50       36 next if not defined $end_line; # Presume search everywhere
154              
155             # If we are analyzing something like 'open $fh ...', 'open FH
156             # ...', or 'open *FH ...' we need to use a scope that includes
157             # the end of the legal range. We just give up and return the
158             # current scope if we can not determine any of the locations
159             # involved. RT #64437
160 13 50       33 return $elem if not $open_loc;
161 13 50       46 my $elem_loc = $elem->location
162             or return $elem;
163 13 50       279 my $last_kid = $elem->child( $LAST_ELEMENT )
164             or return $elem; # What? no children?
165 13 50       144 my $last_kid_loc = $last_kid->location
166             or return $elem;
167             # At this point, the scope we have, even if it is not the
168             # correct scope for the file handle, is big enough that if the
169             # corresponding close() is outside it, it must be a violation.
170             # RT #64437
171 13 100       204 return $elem if $last_kid_loc->[0] > $end_line;
172             }
173 9         53 return $elem; # Whatever the top-level PPI::Node was.
174             }
175              
176             sub _get_opened_fh {
177 48     48   96 my ($tokens) = shift;
178              
179 48         111 my $is_lexical;
180             my $fh;
181              
182 48 100       87 if ( 2 == @{$tokens} ) {
  48 100       134  
183 29 100 100     98 if ('my' eq $tokens->[0] &&
      100        
184             $tokens->[1]->isa('PPI::Token::Symbol') &&
185             $SCALAR_SIGIL eq $tokens->[1]->raw_type) {
186              
187 25         650 $is_lexical = $LOCAL_LEXICAL;
188 25         57 $fh = $tokens->[1];
189             }
190             }
191 19         68 elsif (1 == @{$tokens}) {
192 18         88 my $argument = _unwrap_block( $tokens->[0] );
193 18 100 66     114 if ( $argument->isa('PPI::Token::Symbol') ) {
    100          
194 9         35 my $sigil = $argument->raw_type();
195 9 100       123 if ($SCALAR_SIGIL eq $sigil) {
    100          
196 3         7 $is_lexical = $NON_LOCAL_LEXICAL; # We need to
197             # distinguish between
198             # 'open my $fh ...' and
199             # 'open $fh ...'. RT #64437
200 3         9 $fh = $argument;
201             }
202             elsif ($GLOB_SIGIL eq $sigil) {
203 5         12 $is_lexical = $NOT_LEXICAL;
204 5         13 $fh = $argument;
205             }
206             }
207             elsif ($argument->isa('PPI::Token::Word') && $argument eq uc $argument) {
208 8         149 $is_lexical = $NOT_LEXICAL;
209 8         20 $fh = $argument;
210             }
211             }
212              
213 48         240 return ($is_lexical, $fh);
214             }
215              
216             sub _unwrap_block {
217 18     18   51 my ($element) = @_;
218              
219 18 100       91 return $element if not $element->isa('PPI::Structure::Block');
220              
221 5         19 my @children = $element->schildren();
222 5 50       55 return $element if 1 != @children;
223 5         12 my $child = $children[0];
224              
225 5 50       22 return $child if not $child->isa('PPI::Statement');
226              
227 5         17 my @grandchildren = $child->schildren();
228 5 50       41 return $element if 1 != @grandchildren;
229              
230 5         14 return $grandchildren[0];
231             }
232              
233             1;
234              
235             __END__
236              
237             #-----------------------------------------------------------------------------
238              
239             =pod
240              
241             =for stopwords redeclared
242              
243             =head1 NAME
244              
245             Perl::Critic::Policy::InputOutput::RequireBriefOpen - Close filehandles as soon as possible after opening them.
246              
247              
248             =head1 AFFILIATION
249              
250             This Policy is part of the core L<Perl::Critic|Perl::Critic>
251             distribution.
252              
253              
254             =head1 DESCRIPTION
255              
256             One way that production systems fail unexpectedly is by running out of
257             filehandles. Filehandles are a finite resource on every operating
258             system that I'm aware of, and running out of them is virtually
259             impossible to recover from. The solution is to not run out in the
260             first place. What causes programs to run out of filehandles?
261             Usually, it's leaks: you open a filehandle and forget to close it, or
262             just wait a really long time before closing it.
263              
264             This problem is rarely exposed by test systems, because the tests
265             rarely run long enough or have enough load to hit the filehandle
266             limit. So, the best way to avoid the problem is 1) always close all
267             filehandles that you open and 2) close them as soon as is practical.
268              
269             This policy takes note of calls to C<open()> where there is no
270             matching C<close()> call within C<N> lines of code. If you really
271             need to do a lot of processing on an open filehandle, then you can
272             move that processing to another method like this:
273              
274             sub process_data_file {
275             my ($self, $filename) = @_;
276             open my $fh, '<', $filename
277             or croak 'Failed to read datafile ' . $filename . '; ' . $OS_ERROR;
278             $self->_parse_input_data($fh);
279             close $fh;
280             return;
281             }
282             sub _parse_input_data {
283             my ($self, $fh) = @_;
284             while (my $line = <$fh>) {
285             ...
286             }
287             return;
288             }
289              
290             As a special case, this policy also allows code to return the
291             filehandle after the C<open> instead of closing it. Just like the
292             close, however, that C<return> has to be within the right number of
293             lines. From there, you're on your own to figure out whether the code
294             is promptly closing the filehandle.
295              
296             The STDIN, STDOUT, and STDERR handles are exempt from this policy.
297              
298              
299             =head1 CONFIGURATION
300              
301             This policy allows C<close()> invocations to be up to C<N> lines after
302             their corresponding C<open()> calls, where C<N> defaults to 9. You
303             can override this to set it to a different number with the C<lines>
304             setting. To do this, put entries in a F<.perlcriticrc> file like
305             this:
306              
307             [InputOutput::RequireBriefOpen]
308             lines = 5
309              
310              
311             =head1 CAVEATS
312              
313             =head2 C<IO::File-E<gt>new>
314              
315             This policy only looks for explicit C<open> calls. It does not detect
316             calls to C<CORE::open> or C<IO::File-E<gt>new> or the like.
317              
318              
319             =head2 Is it the right lexical?
320              
321             We don't currently check for redeclared filehandles. So the following
322             code is false negative, for example, because the outer scoped
323             filehandle is not closed:
324              
325             open my $fh, '<', $file1 or croak;
326             if (open my $fh, '<', $file2) {
327             print <$fh>;
328             close $fh;
329             }
330              
331             This is a contrived example, but it isn't uncommon for people to use
332             C<$fh> for the name of the filehandle every time. Perhaps it's time
333             to think of better variable names...
334              
335              
336             =head1 CREDITS
337              
338             Initial development of this policy was supported by a grant from the
339             Perl Foundation.
340              
341              
342             =head1 AUTHOR
343              
344             Chris Dolan <cdolan@cpan.org>
345              
346              
347             =head1 COPYRIGHT
348              
349             Copyright (c) 2007-2021 Chris Dolan. Many rights reserved.
350              
351             This program is free software; you can redistribute it and/or modify
352             it under the same terms as Perl itself. The full text of this license
353             can be found in the LICENSE file included with this module
354              
355             =cut
356              
357             # Local Variables:
358             # mode: cperl
359             # cperl-indent-level: 4
360             # fill-column: 78
361             # indent-tabs-mode: nil
362             # c-indentation-style: bsd
363             # End:
364             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :