useless-if-before-free 7.5 KB

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