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