Commit | Line | Data |
---|---|---|
c84bdaf6 LC |
1 | eval '(exit $?0)' && eval 'exec perl -wST "$0" ${1+"$@"}' |
2 | & eval 'exec perl -wST "$0" $argv:q' | |
3 | if 0; | |
4 | # Detect instances of "if (p) free (p);". | |
5 | # Likewise for "if (p != NULL) free (p);". And with braces. | |
6 | # Also detect "if (NULL != p) free (p);". | |
7 | # And with 0 in place of NULL. | |
8 | ||
9 | my $VERSION = '2009-04-16 15:57'; # UTC | |
10 | # The definition above must lie within the first 8 lines in order | |
11 | # for the Emacs time-stamp write hook (at end) to update it. | |
12 | # If you change this file with Emacs, please let the write hook | |
13 | # do its job. Otherwise, update this string manually. | |
14 | ||
61cd9dc9 | 15 | # Copyright (C) 2008-2010 Free Software Foundation, Inc. |
c84bdaf6 LC |
16 | |
17 | # This program is free software: you can redistribute it and/or modify | |
18 | # it under the terms of the GNU General Public License as published by | |
19 | # the Free Software Foundation, either version 3 of the License, or | |
20 | # (at your option) any later version. | |
21 | ||
22 | # This program is distributed in the hope that it will be useful, | |
23 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
24 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
25 | # GNU General Public License for more details. | |
26 | ||
27 | # You should have received a copy of the GNU General Public License | |
28 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
29 | ||
30 | # Written by Jim Meyering | |
31 | ||
32 | use strict; | |
33 | use warnings; | |
34 | use Getopt::Long; | |
35 | ||
36 | (my $ME = $0) =~ s|.*/||; | |
37 | ||
38 | # use File::Coda; # http://meyering.net/code/Coda/ | |
39 | END { | |
40 | defined fileno STDOUT or return; | |
41 | close STDOUT and return; | |
42 | warn "$ME: failed to close standard output: $!\n"; | |
43 | $? ||= 1; | |
44 | } | |
45 | ||
46 | sub usage ($) | |
47 | { | |
48 | my ($exit_code) = @_; | |
49 | my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); | |
50 | if ($exit_code != 0) | |
51 | { | |
52 | print $STREAM "Try `$ME --help' for more information.\n"; | |
53 | } | |
54 | else | |
55 | { | |
56 | print $STREAM <<EOF; | |
57 | Usage: $ME [OPTIONS] FILE... | |
58 | ||
59 | Detect any instance in FILE of a useless "if" test before a free call, e.g., | |
60 | "if (p) free (p);". Any such test may be safely removed without affecting | |
61 | the semantics of the C code in FILE. Use --name=FOO --name=BAR to also | |
62 | detect free-like functions named FOO and BAR. | |
63 | ||
64 | OPTIONS: | |
65 | ||
66 | --list print only the name of each matching FILE (\0-terminated) | |
67 | --name=N add name N to the list of \`free\'-like functions to detect; | |
68 | may be repeated | |
69 | ||
70 | --help display this help and exit | |
71 | --version output version information and exit | |
72 | ||
73 | Exit status: | |
74 | ||
75 | 0 one or more matches | |
76 | 1 no match | |
77 | 2 an error | |
78 | ||
79 | EXAMPLE: | |
80 | ||
81 | For example, this command prints all removable "if" tests before "free" | |
82 | and "kfree" calls in the linux kernel sources: | |
83 | ||
84 | git ls-files -z |xargs -0 $ME --name=kfree | |
85 | ||
86 | EOF | |
87 | } | |
88 | exit $exit_code; | |
89 | } | |
90 | ||
91 | sub is_NULL ($) | |
92 | { | |
93 | my ($expr) = @_; | |
94 | return ($expr eq 'NULL' || $expr eq '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 | free=xfree | |
181 | git grep -l -z "$free *(" \ | |
182 | | xargs -0 useless-if-before-free -l --name="$free" \ | |
183 | | xargs -0 perl -0x3b -pi -e \ | |
184 | 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s+('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\))/$2/s' | |
185 | ||
186 | # Use the following to remove redundant uses of kfree inside braces. | |
187 | # Note that -0777 puts perl in slurp-whole-file mode; | |
188 | # but we have plenty of memory, these days... | |
189 | free=kfree | |
190 | git grep -l -z "$free *(" \ | |
191 | | xargs -0 useless-if-before-free -l --name="$free" \ | |
192 | | xargs -0 perl -0777 -pi -e \ | |
193 | 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s*\{\s*('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\);)\s*\}[^\n]*$/$2/gms' | |
194 | ||
195 | Be careful that the result of the above transformation is valid. | |
196 | If the matched string is followed by "else", then obviously, it won't be. | |
197 | ||
198 | When modifying files, refuse to process anything other than a regular file. | |
199 | EOF | |
200 | ||
201 | ## Local Variables: | |
202 | ## mode: perl | |
203 | ## indent-tabs-mode: nil | |
204 | ## eval: (add-hook 'write-file-hooks 'time-stamp) | |
205 | ## time-stamp-start: "my $VERSION = '" | |
206 | ## time-stamp-format: "%:y-%02m-%02d %02H:%02M" | |
207 | ## time-stamp-time-zone: "UTC" | |
208 | ## time-stamp-end: "'; # UTC" | |
209 | ## End: |