File Coverage

blib/lib/Perl/Critic/Policy/Modules/ProhibitPOSIXimport.pm
Criterion Covered Total %
statement 122 122 100.0
branch 36 46 78.2
condition 20 25 80.0
subroutine 27 27 100.0
pod 2 2 100.0
total 207 222 93.2


line stmt bran cond sub pod time code
1             # Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2021 Kevin Ryde
2              
3             # Perl-Critic-Pulp is free software; you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by the
5             # Free Software Foundation; either version 3, or (at your option) any later
6             # version.
7             #
8             # Perl-Critic-Pulp is distributed in the hope that it will be useful, but
9             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11             # for more details.
12             #
13             # You should have received a copy of the GNU General Public License along
14             # with Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
15              
16              
17             package Perl::Critic::Policy::Modules::ProhibitPOSIXimport;
18 40     40   32845 use 5.006;
  40         206  
19 40     40   228 use strict;
  40         93  
  40         830  
20 40     40   238 use warnings;
  40         102  
  40         1146  
21 40     40   811 use List::MoreUtils;
  40         14144  
  40         499  
22 40     40   48172 use POSIX ('abort'); # must import something to initialize @POSIX::EXPORT
  40         275679  
  40         335  
23 40     40   60983 use Scalar::Util;
  40         101  
  40         1811  
24              
25 40     40   261 use base 'Perl::Critic::Policy';
  40         92  
  40         5875  
26 40         2419 use Perl::Critic::Utils qw(is_function_call
27 40     40   171981 split_nodes_on_comma);
  40         103  
28 40     40   306 use Perl::Critic::Utils::PPI qw(is_ppi_expression_or_generic_statement);
  40         107  
  40         2085  
29 40     40   938 use Perl::Critic::Pulp::Utils;
  40         103  
  40         2334  
30              
31             # uncomment this to run the ### lines
32             # use Smart::Comments;
33              
34             our $VERSION = 99;
35              
36 40     40   277 use constant _ALLOWED_CALL_COUNT => 15;
  40         111  
  40         3105  
37              
38 40     40   294 use constant supported_parameters => ();
  40         94  
  40         2397  
39 40     40   257 use constant default_severity => $Perl::Critic::Utils::SEVERITY_LOW;
  40         107  
  40         2489  
40 40     40   272 use constant default_themes => qw(pulp efficiency);
  40         108  
  40         2397  
41 40     40   260 use constant applies_to => ('PPI::Statement::Include');
  40         90  
  40         48103  
42              
43             my %posix_function;
44              
45             sub initialize_if_enabled {
46 1     1 1 545751 my ($self, $config) = @_;
47 1         383 @posix_function{@POSIX::EXPORT} = (); # hash slice
48             ### POSIX EXPORT count: scalar(keys %posix_function)
49 1         7 return 1;
50             }
51              
52              
53             sub violates {
54 68     68 1 464792 my ($self, $elem, $document) = @_;
55              
56 68 50 50     282 return unless ($elem->module||'') eq 'POSIX'; # "use POSIX"
57 68 100       2252 return unless (_inc_exporter_imports_type($elem) eq 'default');
58 34 100       96 return if _elem_is_in_package_main($elem); # within main ok
59 30 100       833 return if _count_posix_calls($document) >= _ALLOWED_CALL_COUNT;
60              
61 24         126 return $self->violation
62             ("Don't import the whole of POSIX into a module",
63             '',
64             $elem);
65             }
66              
67             # $inc is a PPI::Statement::Include of a module using Exporter.pm.
68             # Return 'no_import' -- $inc doesn't call import() at all
69             # 'default' -- $inc gets Exporter's default imports
70             # 'explicit' -- $inc chooses certain imports explicitly
71             #
72             sub _inc_exporter_imports_type {
73 128     128   190048 my ($inc) = @_;
74              
75 128 100       466 $inc->type eq 'use'
76             or return 'no_import'; # "require Foo" or "no Foo" don't import
77              
78 124   100     2867 my $mfirst = Perl::Critic::Pulp::Utils::include_module_first_arg ($inc)
79             || return 'default'; # no args or only a version check
80              
81 96         297 my @elems = _elem_and_snext_siblings ($mfirst);
82 96         347 _chomp_trailing_semi (\@elems);
83             ### elems count: scalar(@elems)
84             ### elems: "@elems"
85 96 100 100     640 if (@elems == 1 && _elem_is_empty_list($elems[0])) {
86 16         79 return 'no_import'; # "use Foo ()" doesn't call import() at all
87             }
88              
89 80         223 my @args = _parse_args (@elems);
90 80 100 100     283 if (@args >= 1 && _arg_is_number($args[0])) {
91 48         427 shift @args; # use Foo '123',... Exporter skips version number
92             }
93 80 100       801 return (@args ? 'explicit' : 'default');
94             }
95              
96             # return true if PPI $elem is within the "package main", either an explicit
97             # "package main" or main as the default when no "package" statement at all
98             #
99             sub _elem_is_in_package_main {
100 34     34   94 my ($elem) = @_;
101 34   100     180 my $package = Perl::Critic::Pulp::Utils::elem_package($elem)
102             || return 1; # no package statement
103             ### within_package: "$package"
104 32         126 return ($package->namespace eq 'main'); # explicit "package main"
105             }
106              
107             sub _parse_args {
108 80     80   348 my @first = split_nodes_on_comma (@_);
109             ### first split: scalar(@first)
110             ### @first
111              
112             # if (DEBUG) {
113             # require PPI::Dumper;
114             # foreach my $aref (@first) {
115             # print " aref:\n";
116             # foreach my $elem (@$aref) {
117             # PPI::Dumper->new($elem)->print;
118             # }
119             # }
120             # }
121              
122 80         5322 my @ret;
123 80         206 while (@first) {
124 184         344 my $aref = shift @first;
125 184 50       389 next unless defined $aref;
126 184 50       383 if (@$aref == 1) {
127 184         313 my $elem = $aref->[0];
128 184 100       556 if ($elem->isa('PPI::Structure::List')) {
129 84         584 my @children = $elem->schildren;
130 84 100       605 if (@children == 0) {
131 36         110 next; # empty list elided
132             }
133 48 50       116 if (@children == 1) {
134 48         91 $elem = $children[0];
135 48 50       131 if ($elem->isa('PPI::Statement')) {
136 48         135 @children = $elem->schildren;
137 48 50       362 if (@children) {
138 48         112 unshift @first, split_nodes_on_comma (@children);
139 48         1178 next;
140             }
141             }
142             }
143             }
144             }
145 100         263 push @ret, $aref;
146             }
147              
148             ### final ret: scalar(@ret)
149             # if (DEBUG) {
150             # require PPI::Dumper;
151             # foreach my $aref (@ret) {
152             # print " aref:\n";
153             # foreach my $elem (@$aref) {
154             # PPI::Dumper->new($elem)->print;
155             # }
156             # }
157             # }
158              
159 80         184 return @ret;
160             }
161              
162             sub _chomp_trailing_semi {
163 96     96   200 my ($aref) = @_;
164 96   66     598 while (@$aref
      66        
165             && $aref->[-1]->isa('PPI::Token::Structure')
166             && $aref->[-1]->content eq ';') {
167 48         455 pop @$aref;
168             }
169             }
170              
171             sub _elem_and_snext_siblings {
172 96     96   226 my ($elem) = @_;
173 96         215 my @ret = ($elem);
174 96         380 while ($elem = $elem->snext_sibling) {
175 140         3855 push @ret, $elem;
176             }
177 96         2690 return @ret;
178             }
179              
180             sub _elem_is_empty_list {
181 56     56   118 my ($elem) = @_;
182 56         113 for (;;) {
183 90 100       346 $elem->isa('PPI::Structure::List') || return 0;
184 56         422 my @children = $elem->schildren;
185 56 100       469 if (@children == 0) {
186 16         82 return 1; # empty list
187             }
188 40 50       94 if (@children == 1) {
189 40         76 $elem = $children[0];
190 40 50       116 if ($elem->isa('PPI::Statement')) {
191 40         114 @children = $elem->schildren;
192 40 100       312 if (@children == 1) {
193 34         58 $elem = $children[0];
194 34         71 next;
195             }
196             }
197             }
198 6         26 return 0;
199             }
200             }
201              
202             # $aref is an arrayref of PPI elements which are function arguments.
203             # Return true if the argument is a number, including a numeric string.
204             #
205             # ENHANCE-ME: Do some folding of constant concats or numeric calculations.
206             #
207             sub _arg_is_number {
208 72     72   149 my ($aref) = @_;
209              
210 72 50       184 @$aref == 1 || return 0; # only single elements for now
211 72         125 my $arg = $aref->[0];
212 72   66     562 return ($arg->isa('PPI::Token::Number')
213              
214             || (($arg->isa('PPI::Token::Quote::Single')
215             || $arg->isa('PPI::Token::Quote::Literal'))
216             && Scalar::Util::looks_like_number ($arg->literal)));
217             }
218              
219              
220             # return a count of calls to POSIX module functions within $document
221             sub _count_posix_calls {
222 30     30   72 my ($document) = @_;
223              
224             # function calls like "dup()", with is_function_call() used to exclude
225             # method calls like $x->dup on unrelated objects or classes
226 30   50     119 my $aref = $document->find ('PPI::Token::Word') || [];
227             my $count = List::MoreUtils::true
228 208 100   208   10030 { exists $posix_function{$_->content} && is_function_call($_)
229 30         512 } @$aref;
230             ### count func calls: $count
231              
232             # symbol references \&dup or calls &dup(6)
233 30   100     1093 $aref = $document->find ('PPI::Token::Symbol') || [];
234             $count += List::MoreUtils::true
235 88     88   200 { my $symbol = $_->symbol;
236 88 50       1614 $symbol =~ /^&/ && exists $posix_function{substr($symbol,1)}
237 30         467 } @$aref;
238             ### plus symbols gives: $count
239              
240 30         162 return $count;
241             }
242              
243             1;
244             __END__
245              
246             =for stopwords POSIX kbytes Ryde
247              
248             =head1 NAME
249              
250             Perl::Critic::Policy::Modules::ProhibitPOSIXimport - don't import the whole of POSIX into a module
251              
252             =head1 DESCRIPTION
253              
254             This policy is part of the L<C<Perl::Critic::Pulp>|Perl::Critic::Pulp>
255             add-on. It asks you not to C<use POSIX> with an import of all the symbols
256             from that module if you're only using a few things.
257              
258             package Foo;
259             use POSIX; # bad
260              
261             The aim is to save some memory, and maybe run a bit faster. A full C<POSIX>
262             import adds about 550 symbols to your module and that's about 30 to 40
263             kbytes in Perl 5.10 on a 32-bit system, or about 115 kbytes in Perl 5.8. If
264             lots of modules do this then it adds up.
265              
266             As noted in the C<POSIX> module docs, the way it exports everything by
267             default is an historical accident, not something to encourage.
268              
269             =head2 Allowed Forms
270              
271             A full import is allowed in C<package main>, which is the top-level of a
272             script etc, since in a script you want convenience rather than a bit of
273             memory, at least initially.
274              
275             #!/usr/bin/perl
276             use POSIX; # ok
277              
278             An import of no symbols is allowed and you then add a C<POSIX::> qualifier
279             to each call or constant. Qualifiers like this can make it clear where the
280             function is coming from.
281              
282             package Foo;
283             use POSIX (); # ok
284              
285             my $fd = POSIX::dup(0);
286             if ($! == POSIX::ENOENT())
287              
288             An import of an explicit set of functions and constants is allowed. This
289             allows short names without the memory penalty of a full import. However it
290             can be error-prone to update the imports with what you actually use (see
291             C<ProhibitCallsToUndeclaredSubs> for some checking).
292              
293             package Foo;
294             use POSIX qw(dup ENOENT); # ok
295             ...
296             my $fd = dup(0);
297              
298             A full import is allowed in a module if there's 15 or more calls to C<POSIX>
299             module functions. This rule might change or be configurable in the future,
300             but the intention is that a module making heavy use of C<POSIX> shouldn't be
301             burdened by a C<POSIX::> on every call or by maintaining a list of explicit
302             imports.
303              
304             package Foo;
305             use POSIX; # ok
306             ...
307             tzset(); dup(1)... # 15 or more calls to POSIX stuff
308              
309             =head2 Disabling
310              
311             If you don't care this sort of thing you can always disable
312             C<ProhibitPOSIXimport> from your F<.perlcriticrc> in the usual way (see
313             L<Perl::Critic/CONFIGURATION>),
314              
315             [-Modules::ProhibitPOSIXimport]
316              
317             =head1 SEE ALSO
318              
319             L<POSIX>,
320             L<Perl::Critic::Pulp>,
321             L<Perl::Critic>,
322             L<Perl::Critic::Policy::Subroutines::ProhibitCallsToUndeclaredSubs>
323              
324             =head1 HOME PAGE
325              
326             L<http://user42.tuxfamily.org/perl-critic-pulp/index.html>
327              
328             =head1 COPYRIGHT
329              
330             Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2021 Kevin Ryde
331              
332             Perl-Critic-Pulp is free software; you can redistribute it and/or modify it
333             under the terms of the GNU General Public License as published by the Free
334             Software Foundation; either version 3, or (at your option) any later
335             version.
336              
337             Perl-Critic-Pulp is distributed in the hope that it will be useful, but
338             WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
339             or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
340             more details.
341              
342             You should have received a copy of the GNU General Public License along with
343             Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
344              
345             =cut