File Coverage

blib/lib/Perl/Critic/Policy/InputOutput/RequireBriefOpen.pm
Criterion Covered Total %
statement 26 109 23.8
branch 1 84 1.1
condition 0 23 0.0
subroutine 12 18 66.6
pod 4 5 80.0
total 43 239 17.9


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::InputOutput::RequireBriefOpen;
2              
3 40     40   28572 use 5.010001;
  40         194  
4 40     40   252 use strict;
  40         134  
  40         873  
5 40     40   247 use warnings;
  40         166  
  40         1091  
6              
7 40     40   233 use Readonly;
  40         134  
  40         2220  
8              
9 40     40   312 use List::SomeUtils qw(any);
  40         107  
  40         2178  
10              
11 40         2217 use Perl::Critic::Utils qw( :severities :classification
12             hashify parse_arg_list
13 40     40   328 );
  40         145  
14 40     40   14570 use parent 'Perl::Critic::Policy';
  40         457  
  40         276  
15              
16             our $VERSION = '1.150';
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 90     90 0 2042 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 74     74 1 285 sub default_severity { return $SEVERITY_HIGH }
62 86     86 1 329 sub default_themes { return qw< core pbp maintenance > }
63 32     32 1 88 sub applies_to { return 'PPI::Token::Word' }
64              
65             #-----------------------------------------------------------------------------
66              
67             sub violates {
68 346     346 1 578 my ( $self, $elem, undef ) = @_;
69              
70             # Is it a call to open?
71 346 50       579 $OPEN_BUILTIN{$elem->content()} or return;
72 0 0         return if ! is_function_call($elem);
73 0           my @open_args = parse_arg_list($elem);
74 0 0         return if 2 > @open_args; # not a valid call to open()
75              
76 0           my ($is_lexical, $fh) = _get_opened_fh($open_args[0]);
77 0 0         return if not $fh;
78 0 0         return if $fh =~ m< \A [*]? STD (?: IN|OUT|ERR ) \z >xms;
79              
80 0           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 0 0 0       if (is_function_call($close_token)) {
    0          
85 0           my @close_args = parse_arg_list($close_token);
86              
87 0           my $close_parameter = $close_args[0];
88 0 0         if ('ARRAY' eq ref $close_parameter) {
89 0           $close_parameter = ${$close_parameter}[0];
  0            
90             }
91 0 0         if ( $close_parameter ) {
92 0           $close_parameter = "$close_parameter";
93 0 0         return if $fh eq $close_parameter;
94              
95 0 0   0     if ( any { m< \A [*] >xms } ($fh, $close_parameter) ) {
  0            
96 0           (my $stripped_fh = $fh) =~ s< \A [*] ><>xms;
97 0           (my $stripped_parameter = $close_parameter) =~
98             s< \A [*] ><>xms;
99              
100 0 0         return if $stripped_fh eq $stripped_parameter;
101             }
102             }
103             }
104             elsif ($is_lexical && is_method_call($close_token)) {
105 0           my $tok = $close_token->sprevious_sibling->sprevious_sibling;
106 0 0         return if $fh eq $tok;
107             }
108             }
109              
110 0           return $self->violation( $DESC, $EXPL, $elem );
111             }
112              
113             sub _find_close_invocations_or_return {
114 0     0     my ($self, $elem, $is_lexical) = @_;
115              
116 0           my $parent = $self->_get_scope( $elem, $is_lexical );
117 0 0         return if !$parent; # I can't think of a scenario where this would happen
118              
119 0           my $open_loc = $elem->location;
120             # we don't actually allow _lines to be zero or undef, but maybe we will
121 0 0         my $end_line = $self->{_lines} ? $open_loc->[0] + $self->{_lines} : undef;
122              
123             my $closes = $parent->find(sub {
124             ##no critic (ProhibitExplicitReturnUndef)
125 0     0     my ($parent, $candidate) = @_; ## no critic(Variables::ProhibitReusedNames)
126 0 0         return undef if $candidate->isa('PPI::Statement::Sub');
127 0           my $candidate_loc = $candidate->location;
128 0 0         return undef if !defined $candidate_loc->[0];
129 0 0         return 0 if $candidate_loc->[0] < $open_loc->[0];
130 0 0 0       return 0 if $candidate_loc->[0] == $open_loc->[0] && $candidate_loc->[1] <= $open_loc->[1];
131 0 0 0       return undef if defined $end_line && $candidate_loc->[0] > $end_line;
132 0 0         return 0 if !$candidate->isa('PPI::Token::Word');
133 0   0       return $CLOSE_BUILTIN{ $candidate->content() } || 0;
134 0           });
135 0 0         return @{$closes || []};
  0            
136             }
137              
138             sub _get_scope {
139 0     0     my ( $self, $elem, $is_lexical ) = @_;
140              
141 0           my $open_loc = $elem->location;
142             my $end_line = ( $self->{_lines} && defined $open_loc->[0] ) ?
143             $open_loc->[0] + $self->{_lines} :
144 0 0 0       undef;
145              
146 0           while ( my $dad = $elem->parent) {
147 0           $elem = $dad;
148 0 0         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 0 0         return $elem if $LOCAL_LEXICAL == $is_lexical;
153 0 0         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 0 0         return $elem if not $open_loc;
161 0 0         $elem->location
162             or return $elem;
163 0 0         my $last_kid = $elem->child( $LAST_ELEMENT )
164             or return $elem; # What? no children?
165 0 0         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 0 0         return $elem if $last_kid_loc->[0] > $end_line;
172             }
173 0           return $elem; # Whatever the top-level PPI::Node was.
174             }
175              
176             sub _get_opened_fh {
177 0     0     my ($tokens) = shift;
178              
179 0           my $is_lexical;
180             my $fh;
181              
182 0 0         if ( 2 == @{$tokens} ) {
  0 0          
183 0 0 0       if ('my' eq $tokens->[0] &&
      0        
184             $tokens->[1]->isa('PPI::Token::Symbol') &&
185             $SCALAR_SIGIL eq $tokens->[1]->raw_type) {
186              
187 0           $is_lexical = $LOCAL_LEXICAL;
188 0           $fh = $tokens->[1];
189             }
190             }
191 0           elsif (1 == @{$tokens}) {
192 0           my $argument = _unwrap_block( $tokens->[0] );
193 0 0 0       if ( $argument->isa('PPI::Token::Symbol') ) {
    0          
194 0           my $sigil = $argument->raw_type();
195 0 0         if ($SCALAR_SIGIL eq $sigil) {
    0          
196 0           $is_lexical = $NON_LOCAL_LEXICAL; # We need to
197             # distinguish between
198             # 'open my $fh ...' and
199             # 'open $fh ...'. RT #64437
200 0           $fh = $argument;
201             }
202             elsif ($GLOB_SIGIL eq $sigil) {
203 0           $is_lexical = $NOT_LEXICAL;
204 0           $fh = $argument;
205             }
206             }
207             elsif ($argument->isa('PPI::Token::Word') && $argument eq uc $argument) {
208 0           $is_lexical = $NOT_LEXICAL;
209 0           $fh = $argument;
210             }
211             }
212              
213 0           return ($is_lexical, $fh);
214             }
215              
216             sub _unwrap_block {
217 0     0     my ($element) = @_;
218              
219 0 0         return $element if not $element->isa('PPI::Structure::Block');
220              
221 0           my @children = $element->schildren();
222 0 0         return $element if 1 != @children;
223 0           my $child = $children[0];
224              
225 0 0         return $child if not $child->isa('PPI::Statement');
226              
227 0           my @grandchildren = $child->schildren();
228 0 0         return $element if 1 != @grandchildren;
229              
230 0           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-2023 Chris Dolan
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 :