1#!/bin/sh 2#! -*-perl-*- 3 4# Detect instances of "if (p) free (p);". 5# Likewise "if (p != 0)", "if (0 != p)", or with NULL; and with braces. 6 7# Copyright (C) 2008-2021 Free Software Foundation, Inc. 8# 9# This program is free software: you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation, either version 3 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program. If not, see <https://www.gnu.org/licenses/>. 21# 22# Written by Jim Meyering 23 24# This is a prologue that allows to run a perl script as an executable 25# on systems that are compliant to a POSIX version before POSIX:2017. 26# On such systems, the usual invocation of an executable through execlp() 27# or execvp() fails with ENOEXEC if it is a script that does not start 28# with a #! line. The script interpreter mentioned in the #! line has 29# to be /bin/sh, because on GuixSD systems that is the only program that 30# has a fixed file name. The second line is essential for perl and is 31# also useful for editing this file in Emacs. The next two lines below 32# are valid code in both sh and perl. When executed by sh, they re-execute 33# the script through the perl program found in $PATH. The '-x' option 34# is essential as well; without it, perl would re-execute the script 35# through /bin/sh. When executed by perl, the next two lines are a no-op. 36eval 'exec perl -wSx "$0" "$@"' 37 if 0; 38 39my $VERSION = '2021-04-11 10:11'; # UTC 40# The definition above must lie within the first 8 lines in order 41# for the Emacs time-stamp write hook (at end) to update it. 42# If you change this file with Emacs, please let the write hook 43# do its job. Otherwise, update this string manually. 44 45my $copyright_year = '2021'; 46 47use strict; 48use warnings; 49use Getopt::Long; 50 51(my $ME = $0) =~ s|.*/||; 52 53# use File::Coda; # https://meyering.net/code/Coda/ 54END { 55 defined fileno STDOUT or return; 56 close STDOUT and return; 57 warn "$ME: failed to close standard output: $!\n"; 58 $? ||= 1; 59} 60 61sub usage ($) 62{ 63 my ($exit_code) = @_; 64 my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); 65 if ($exit_code != 0) 66 { 67 print $STREAM "Try '$ME --help' for more information.\n"; 68 } 69 else 70 { 71 print $STREAM <<EOF; 72Usage: $ME [OPTIONS] FILE... 73 74Detect any instance in FILE of a useless "if" test before a free call, e.g., 75"if (p) free (p);". Any such test may be safely removed without affecting 76the semantics of the C code in FILE. Use --name=FOO --name=BAR to also 77detect free-like functions named FOO and BAR. 78 79OPTIONS: 80 81 --list print only the name of each matching FILE (\\0-terminated) 82 --name=N add name N to the list of \'free\'-like functions to detect; 83 may be repeated 84 85 --help display this help and exit 86 --version output version information and exit 87 88Exit status: 89 90 0 one or more matches 91 1 no match 92 2 an error 93 94EXAMPLE: 95 96For example, this command prints all removable "if" tests before "free" 97and "kfree" calls in the linux kernel sources: 98 99 git ls-files -z |xargs -0 $ME --name=kfree 100 101EOF 102 } 103 exit $exit_code; 104} 105 106sub is_NULL ($) 107{ 108 my ($expr) = @_; 109 return ($expr eq 'NULL' || $expr eq '0'); 110} 111 112{ 113 sub EXIT_MATCH {0} 114 sub EXIT_NO_MATCH {1} 115 sub EXIT_ERROR {2} 116 my $err = EXIT_NO_MATCH; 117 118 my $list; 119 my @name = qw(free); 120 GetOptions 121 ( 122 help => sub { usage 0 }, 123 version => 124 sub 125 { 126 print "$ME version $VERSION\n"; 127 print "Copyright (C) $copyright_year Free Software Foundation, Inc.\n"; 128 print "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.\n" 129 . "This is free software: you are free to change and redistribute it.\n" 130 . "There is NO WARRANTY, to the extent permitted by law.\n"; 131 print "\n"; 132 my $author = "Jim Meyering"; 133 print "Written by $author.\n"; 134 exit 135 }, 136 list => \$list, 137 'name=s@' => \@name, 138 ) or usage 1; 139 140 # Make sure we have the right number of non-option arguments. 141 # Always tell the user why we fail. 142 @ARGV < 1 143 and (warn "$ME: missing FILE argument\n"), usage EXIT_ERROR; 144 145 my $or = join '|', @name; 146 my $regexp = qr/(?:$or)/; 147 148 # Set the input record separator. 149 # Note: this makes it impractical to print line numbers. 150 $/ = '"'; 151 152 my $found_match = 0; 153 FILE: 154 foreach my $file (@ARGV) 155 { 156 open FH, '<', $file 157 or (warn "$ME: can't open '$file' for reading: $!\n"), 158 $err = EXIT_ERROR, next; 159 while (defined (my $line = <FH>)) 160 { 161 # Skip non-matching lines early to save time 162 $line =~ /\bif\b/ 163 or next; 164 while ($line =~ 165 /\b(if\s*\(\s*([^)]+?)(?:\s*!=\s*([^)]+?))?\s*\) 166 # 1 2 3 167 (?: \s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)\s*;| 168 \s*\{\s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)\s*;\s*\}))/sxg) 169 { 170 my $all = $1; 171 my ($lhs, $rhs) = ($2, $3); 172 my ($free_opnd, $braced_free_opnd) = ($4, $5); 173 my $non_NULL; 174 if (!defined $rhs) { $non_NULL = $lhs } 175 elsif (is_NULL $rhs) { $non_NULL = $lhs } 176 elsif (is_NULL $lhs) { $non_NULL = $rhs } 177 else { next } 178 179 # Compare the non-NULL part of the "if" expression and the 180 # free'd expression, without regard to white space. 181 $non_NULL =~ tr/ \t//d; 182 my $e2 = defined $free_opnd ? $free_opnd : $braced_free_opnd; 183 $e2 =~ tr/ \t//d; 184 if ($non_NULL eq $e2) 185 { 186 $found_match = 1; 187 $list 188 and (print "$file\0"), next FILE; 189 print "$file: $all\n"; 190 } 191 } 192 } 193 } 194 continue 195 { 196 close FH; 197 } 198 199 $found_match && $err == EXIT_NO_MATCH 200 and $err = EXIT_MATCH; 201 202 exit $err; 203} 204 205my $foo = <<'EOF'; 206# The above is to *find* them. 207# This adjusts them, removing the unnecessary "if (p)" part. 208 209# FIXME: do something like this as an option (doesn't do braces): 210free=xfree 211git grep -l -z "$free *(" \ 212 | xargs -0 useless-if-before-free -l --name="$free" \ 213 | xargs -0 perl -0x3b -pi -e \ 214 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s+('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\)\s*;)/$2/s' 215 216# Use the following to remove redundant uses of kfree inside braces. 217# Note that -0777 puts perl in slurp-whole-file mode; 218# but we have plenty of memory, these days... 219free=kfree 220git grep -l -z "$free *(" \ 221 | xargs -0 useless-if-before-free -l --name="$free" \ 222 | xargs -0 perl -0777 -pi -e \ 223 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s*\{\s*('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\);)\s*\}[^\n]*$/$2/gms' 224 225Be careful that the result of the above transformation is valid. 226If the matched string is followed by "else", then obviously, it won't be. 227 228When modifying files, refuse to process anything other than a regular file. 229EOF 230 231## Local Variables: 232## mode: perl 233## indent-tabs-mode: nil 234## eval: (add-hook 'before-save-hook 'time-stamp) 235## time-stamp-line-limit: 50 236## time-stamp-start: "my $VERSION = '" 237## time-stamp-format: "%:y-%02m-%02d %02H:%02M" 238## time-stamp-time-zone: "UTC0" 239## time-stamp-end: "'; # UTC" 240## End: 241