useless-if-before-free: recognize more variants
[gnulib.git] / build-aux / useless-if-before-free
1 #!/usr/bin/perl -T
2 # Detect instances of "if (p) free (p);".
3 # Likewise for "if (p != NULL) free (p);".  And with braces.
4 # Also detect "if (NULL != p) free (p);".
5 # And with 0 or "(void *)0" in place of NULL.
6
7 my $VERSION = '2009-01-28 08:16'; # UTC
8 # The definition above must lie within the first 8 lines in order
9 # for the Emacs time-stamp write hook (at end) to update it.
10 # If you change this file with Emacs, please let the write hook
11 # do its job.  Otherwise, update this string manually.
12
13 # Copyright (C) 2008, 2009 Free Software Foundation, Inc.
14
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
19
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 # GNU General Public License for more details.
24
25 # You should have received a copy of the GNU General Public License
26 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
28 # Written by Jim Meyering
29
30 use strict;
31 use warnings;
32 use Getopt::Long;
33
34 (my $ME = $0) =~ s|.*/||;
35
36 # use File::Coda; # http://meyering.net/code/Coda/
37 END {
38   defined fileno STDOUT or return;
39   close STDOUT and return;
40   warn "$ME: failed to close standard output: $!\n";
41   $? ||= 1;
42 }
43
44 sub usage ($)
45 {
46   my ($exit_code) = @_;
47   my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
48   if ($exit_code != 0)
49     {
50       print $STREAM "Try `$ME --help' for more information.\n";
51     }
52   else
53     {
54       print $STREAM <<EOF;
55 Usage: $ME [OPTIONS] FILE...
56
57 Detect any instance in FILE of a useless "if" test before a free call, e.g.,
58 "if (p) free (p);".  Any such test may be safely removed without affecting
59 the semantics of the C code in FILE.  Use --name=FOO --name=BAR to also
60 detect free-like functions named FOO and BAR.
61
62 OPTIONS:
63
64    --list       print only the name of each matching FILE (\0-terminated)
65    --name=N     add name N to the list of \`free\'-like functions to detect;
66                   may be repeated
67
68    --help       display this help and exit
69    --version    output version information and exit
70
71 Exit status:
72
73   0   one or more matches
74   1   no match
75   2   an error
76
77 EXAMPLE:
78
79 For example, this command prints all removable "if" tests before "free"
80 and "kfree" calls in the linux kernel sources:
81
82     git ls-files -z |xargs -0 $ME --name=kfree
83
84 EOF
85     }
86   exit $exit_code;
87 }
88
89 sub is_NULL ($)
90 {
91   my ($expr) = @_;
92   return ($expr eq 'NULL'
93           || $expr eq '0'
94           || $expr =~ /^\(\s*(char|void)\s*\*\s*\)\s*0$/);
95 }
96
97 {
98   sub EXIT_MATCH {0}
99   sub EXIT_NO_MATCH {1}
100   sub EXIT_ERROR {2}
101   my $err = EXIT_NO_MATCH;
102
103   my $list;
104   my @name = qw(free);
105   GetOptions
106     (
107      help => sub { usage 0 },
108      version => sub { print "$ME version $VERSION\n"; exit },
109      list => \$list,
110      'name=s@' => \@name,
111     ) or usage 1;
112
113   # Make sure we have the right number of non-option arguments.
114   # Always tell the user why we fail.
115   @ARGV < 1
116     and (warn "$ME: missing FILE argument\n"), usage EXIT_ERROR;
117
118   my $or = join '|', @name;
119   my $regexp = qr/(?:$or)/;
120
121   # Set the input record separator.
122   # Note: this makes it impractical to print line numbers.
123   $/ = '"';
124
125   my $found_match = 0;
126  FILE:
127   foreach my $file (@ARGV)
128     {
129       open FH, '<', $file
130         or (warn "$ME: can't open `$file' for reading: $!\n"),
131           $err = EXIT_ERROR, next;
132       while (defined (my $line = <FH>))
133         {
134           while ($line =~
135               /\b(if\s*\(\s*([^)]+?)(?:\s*!=\s*([^)]+?))?\s*\)
136               #  1          2                  3
137                (?:   \s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)|
138                 \s*\{\s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)\s*;\s*\}))/sxg)
139             {
140               my $all = $1;
141               my ($lhs, $rhs) = ($2, $3);
142               my ($free_opnd, $braced_free_opnd) = ($4, $5);
143               my $non_NULL;
144               if (!defined $rhs) { $non_NULL = $lhs }
145               elsif (is_NULL $rhs) { $non_NULL = $lhs }
146               elsif (is_NULL $lhs) { $non_NULL = $rhs }
147               else { next }
148
149               # Compare the non-NULL part of the "if" expression and the
150               # free'd expression, without regard to white space.
151               $non_NULL =~ tr/ \t//d;
152               my $e2 = defined $free_opnd ? $free_opnd : $braced_free_opnd;
153               $e2 =~ tr/ \t//d;
154               if ($non_NULL eq $e2)
155                 {
156                   $found_match = 1;
157                   $list
158                     and (print "$file\0"), next FILE;
159                   print "$file: $all\n";
160                 }
161             }
162         }
163     }
164   continue
165     {
166       close FH;
167     }
168
169   $found_match && $err == EXIT_NO_MATCH
170     and $err = EXIT_MATCH;
171
172   exit $err;
173 }
174
175 my $foo = <<'EOF';
176 # The above is to *find* them.
177 # This adjusts them, removing the unnecessary "if (p)" part.
178
179 # FIXME: do something like this as an option (doesn't do braces):
180 useless-if-before-free -l $(lid -knone free) | xargs -0 \
181   perl -0x3b -pi -e \
182    's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*NULL)?\s*\)\s+(free\s*\((?:\s*\([^)]+\))?\s*\1\s*\))/$2/s'
183
184 # Or, with git:
185 git ls-files -z |xargs -0 perl -0x3b -pi -e '...'
186
187 Be careful that the result of the above transformation is valid.
188 If the matched string is followed by "else", then obviously, it won't be.
189
190 When modifying files, refuse to process anything other than a regular file.
191 EOF
192
193 ## Local Variables:
194 ## indent-tabs-mode: nil
195 ## eval: (add-hook 'write-file-hooks 'time-stamp)
196 ## time-stamp-start: "my $VERSION = '"
197 ## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
198 ## time-stamp-time-zone: "UTC"
199 ## time-stamp-end: "'; # UTC"
200 ## End: