1eval '(exit $?0)' && eval 'exec perl -wST "$0" "$@"'
2  & eval 'exec perl -wST "$0" $argv:q'
3    if 0;
4# Detect instances of "if (p) free (p);".
5# Likewise "if (p != 0)", "if (0 != p)", or with NULL; and with braces.
6
7my $VERSION = '2017-09-13 06:45'; # 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-2017 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 <https://www.gnu.org/licenses/>.
27
28# Written by Jim Meyering
29
30use strict;
31use warnings;
32use Getopt::Long;
33
34(my $ME = $0) =~ s|.*/||;
35
36# use File::Coda; # https://meyering.net/code/Coda/
37END {
38  defined fileno STDOUT or return;
39  close STDOUT and return;
40  warn "$ME: failed to close standard output: $!\n";
41  $? ||= 1;
42}
43
44sub 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;
55Usage: $ME [OPTIONS] FILE...
56
57Detect 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
59the semantics of the C code in FILE.  Use --name=FOO --name=BAR to also
60detect free-like functions named FOO and BAR.
61
62OPTIONS:
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
71Exit status:
72
73  0   one or more matches
74  1   no match
75  2   an error
76
77EXAMPLE:
78
79For example, this command prints all removable "if" tests before "free"
80and "kfree" calls in the linux kernel sources:
81
82    git ls-files -z |xargs -0 $ME --name=kfree
83
84EOF
85    }
86  exit $exit_code;
87}
88
89sub is_NULL ($)
90{
91  my ($expr) = @_;
92  return ($expr eq 'NULL' || $expr eq '0');
93}
94
95{
96  sub EXIT_MATCH {0}
97  sub EXIT_NO_MATCH {1}
98  sub EXIT_ERROR {2}
99  my $err = EXIT_NO_MATCH;
100
101  my $list;
102  my @name = qw(free);
103  GetOptions
104    (
105     help => sub { usage 0 },
106     version => sub { print "$ME version $VERSION\n"; exit },
107     list => \$list,
108     'name=s@' => \@name,
109    ) or usage 1;
110
111  # Make sure we have the right number of non-option arguments.
112  # Always tell the user why we fail.
113  @ARGV < 1
114    and (warn "$ME: missing FILE argument\n"), usage EXIT_ERROR;
115
116  my $or = join '|', @name;
117  my $regexp = qr/(?:$or)/;
118
119  # Set the input record separator.
120  # Note: this makes it impractical to print line numbers.
121  $/ = '"';
122
123  my $found_match = 0;
124 FILE:
125  foreach my $file (@ARGV)
126    {
127      open FH, '<', $file
128        or (warn "$ME: can't open '$file' for reading: $!\n"),
129          $err = EXIT_ERROR, next;
130      while (defined (my $line = <FH>))
131        {
132          # Skip non-matching lines early to save time
133          $line =~ /\bif\b/
134            or next;
135          while ($line =~
136              /\b(if\s*\(\s*([^)]+?)(?:\s*!=\s*([^)]+?))?\s*\)
137              #  1          2                  3
138               (?:   \s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)\s*;|
139                \s*\{\s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)\s*;\s*\}))/sxg)
140            {
141              my $all = $1;
142              my ($lhs, $rhs) = ($2, $3);
143              my ($free_opnd, $braced_free_opnd) = ($4, $5);
144              my $non_NULL;
145              if (!defined $rhs) { $non_NULL = $lhs }
146              elsif (is_NULL $rhs) { $non_NULL = $lhs }
147              elsif (is_NULL $lhs) { $non_NULL = $rhs }
148              else { next }
149
150              # Compare the non-NULL part of the "if" expression and the
151              # free'd expression, without regard to white space.
152              $non_NULL =~ tr/ \t//d;
153              my $e2 = defined $free_opnd ? $free_opnd : $braced_free_opnd;
154              $e2 =~ tr/ \t//d;
155              if ($non_NULL eq $e2)
156                {
157                  $found_match = 1;
158                  $list
159                    and (print "$file\0"), next FILE;
160                  print "$file: $all\n";
161                }
162            }
163        }
164    }
165  continue
166    {
167      close FH;
168    }
169
170  $found_match && $err == EXIT_NO_MATCH
171    and $err = EXIT_MATCH;
172
173  exit $err;
174}
175
176my $foo = <<'EOF';
177# The above is to *find* them.
178# This adjusts them, removing the unnecessary "if (p)" part.
179
180# FIXME: do something like this as an option (doesn't do braces):
181free=xfree
182git grep -l -z "$free *(" \
183  | xargs -0 useless-if-before-free -l --name="$free" \
184  | xargs -0 perl -0x3b -pi -e \
185   's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s+('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\)\s*;)/$2/s'
186
187# Use the following to remove redundant uses of kfree inside braces.
188# Note that -0777 puts perl in slurp-whole-file mode;
189# but we have plenty of memory, these days...
190free=kfree
191git grep -l -z "$free *(" \
192  | xargs -0 useless-if-before-free -l --name="$free" \
193  | xargs -0 perl -0777 -pi -e \
194     's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s*\{\s*('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\);)\s*\}[^\n]*$/$2/gms'
195
196Be careful that the result of the above transformation is valid.
197If the matched string is followed by "else", then obviously, it won't be.
198
199When modifying files, refuse to process anything other than a regular file.
200EOF
201
202## Local Variables:
203## mode: perl
204## indent-tabs-mode: nil
205## eval: (add-hook 'write-file-hooks 'time-stamp)
206## time-stamp-start: "my $VERSION = '"
207## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
208## time-stamp-time-zone: "UTC0"
209## time-stamp-end: "'; # UTC"
210## End:
211