1eval '(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 "if (p != 0)", "if (0 != p)", or with NULL; and with braces. 6 7my $VERSION = '2012-01-06 07:23'; # 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-2012 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 30use strict; 31use warnings; 32use Getopt::Long; 33 34(my $ME = $0) =~ s|.*/||; 35 36# use File::Coda; # http://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 while ($line =~ 133 /\b(if\s*\(\s*([^)]+?)(?:\s*!=\s*([^)]+?))?\s*\) 134 # 1 2 3 135 (?: \s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)\s*;| 136 \s*\{\s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)\s*;\s*\}))/sxg) 137 { 138 my $all = $1; 139 my ($lhs, $rhs) = ($2, $3); 140 my ($free_opnd, $braced_free_opnd) = ($4, $5); 141 my $non_NULL; 142 if (!defined $rhs) { $non_NULL = $lhs } 143 elsif (is_NULL $rhs) { $non_NULL = $lhs } 144 elsif (is_NULL $lhs) { $non_NULL = $rhs } 145 else { next } 146 147 # Compare the non-NULL part of the "if" expression and the 148 # free'd expression, without regard to white space. 149 $non_NULL =~ tr/ \t//d; 150 my $e2 = defined $free_opnd ? $free_opnd : $braced_free_opnd; 151 $e2 =~ tr/ \t//d; 152 if ($non_NULL eq $e2) 153 { 154 $found_match = 1; 155 $list 156 and (print "$file\0"), next FILE; 157 print "$file: $all\n"; 158 } 159 } 160 } 161 } 162 continue 163 { 164 close FH; 165 } 166 167 $found_match && $err == EXIT_NO_MATCH 168 and $err = EXIT_MATCH; 169 170 exit $err; 171} 172 173my $foo = <<'EOF'; 174# The above is to *find* them. 175# This adjusts them, removing the unnecessary "if (p)" part. 176 177# FIXME: do something like this as an option (doesn't do braces): 178free=xfree 179git grep -l -z "$free *(" \ 180 | xargs -0 useless-if-before-free -l --name="$free" \ 181 | xargs -0 perl -0x3b -pi -e \ 182 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s+('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\)\s*;)/$2/s' 183 184# Use the following to remove redundant uses of kfree inside braces. 185# Note that -0777 puts perl in slurp-whole-file mode; 186# but we have plenty of memory, these days... 187free=kfree 188git grep -l -z "$free *(" \ 189 | xargs -0 useless-if-before-free -l --name="$free" \ 190 | xargs -0 perl -0777 -pi -e \ 191 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s*\{\s*('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\);)\s*\}[^\n]*$/$2/gms' 192 193Be careful that the result of the above transformation is valid. 194If the matched string is followed by "else", then obviously, it won't be. 195 196When modifying files, refuse to process anything other than a regular file. 197EOF 198 199## Local Variables: 200## mode: perl 201## indent-tabs-mode: nil 202## eval: (add-hook 'write-file-hooks 'time-stamp) 203## time-stamp-start: "my $VERSION = '" 204## time-stamp-format: "%:y-%02m-%02d %02H:%02M" 205## time-stamp-time-zone: "UTC" 206## time-stamp-end: "'; # UTC" 207## End: 208