useless-if-before-free 4.7 KB
Newer Older
1 2
#!/usr/bin/perl -T
# Detect instances of "if (p) free (p);".
3
# Likewise for "if (p != NULL) free (p);".  And with braces.
4

J
Jim Meyering 已提交
5
my $VERSION = '2008-04-29 19:56'; # UTC
6 7 8 9 10
# The definition above must lie within the first 8 lines in order
# for the Emacs time-stamp write hook (at end) to update it.
# If you change this file with Emacs, please let the write hook
# do its job.  Otherwise, update this string manually.

11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# Copyright (C) 2008 Free Software Foundation, Inc.

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Written by Jim Meyering

28 29 30 31 32 33
use strict;
use warnings;
use Getopt::Long;

(my $ME = $0) =~ s|.*/||;

34 35 36 37 38 39 40
# use File::Coda; # http://meyering.net/code/Coda/
END {
  defined fileno STDOUT or return;
  close STDOUT and return;
  warn "$ME: failed to close standard output: $!\n";
  $? ||= 1;
}
41 42 43 44 45 46 47 48 49 50 51 52 53 54

sub usage ($)
{
  my ($exit_code) = @_;
  my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
  if ($exit_code != 0)
    {
      print $STREAM "Try `$ME --help' for more information.\n";
    }
  else
    {
      print $STREAM <<EOF;
Usage: $ME [OPTIONS] FILE...

55 56 57 58 59
Detect any instance in FILE of a useless "if" test before a free call, e.g.,
"if (p) free (p);".  Any such test may be safely removed without affecting
the semantics of the C code in FILE.  Use --name=FOO --name=BAR to also
detect free-like functions named FOO and BAR.

60 61
OPTIONS:

62
   --list       print only the name of each matching FILE (\0-terminated)
63 64 65 66 67
   --name=N     add name N to the list of `free'-like functions to detect;
                  may be repeated

   --help       display this help and exit
   --version    output version information and exit
68 69 70 71 72 73

Exit status:

  0   no match
  1   one or more matches
  2   an error
74 75 76

EXAMPLE:

77 78 79 80
For example, this command prints all removable "if" tests before "free"
and "kfree" calls in the linux kernel sources:

    git ls-files -z |xargs -0 $ME --name=kfree
81 82 83 84 85 86 87

EOF
    }
  exit $exit_code;
}

{
88 89 90 91 92 93
  sub EXIT_MATCH {0}
  sub EXIT_NO_MATCH {1}
  sub EXIT_ERROR {2}
  my $err = EXIT_NO_MATCH;

  my $list;
94 95 96 97 98
  my @name = qw(free);
  GetOptions
    (
     help => sub { usage 0 },
     version => sub { print "$ME version $VERSION\n"; exit },
99
     list => \$list,
100 101 102 103 104 105
     'name=s@' => \@name,
    ) or usage 1;

  # Make sure we have the right number of non-option arguments.
  # Always tell the user why we fail.
  @ARGV < 1
106
    and (warn "$ME: missing FILE argument\n"), usage EXIT_ERROR;
107 108 109 110 111

  my $or = join '|', @name;
  my $regexp = qr/(?:$or)/;

  # Set the input record separator.
112
  # Note: this makes it impractical to print line numbers.
113 114 115
  $/ = '"';

  my $found_match = 0;
116
 FILE:
117 118 119
  foreach my $file (@ARGV)
    {
      open FH, '<', $file
120 121
        or (warn "$ME: can't open `$file' for reading: $!\n"),
          $err = EXIT_ERROR, next;
122 123
      while (defined (my $line = <FH>))
        {
J
Jim Meyering 已提交
124
          while ($line =~
125
              /\b(if\s*\(\s*(\S+?)(?:\s*!=\s*NULL)?\s*\)
J
Jim Meyering 已提交
126
               (?:   \s*$regexp\s*\((?:\s*\([^)]+\))?\s*\2\s*\)|
J
Jim Meyering 已提交
127
                \s*\{\s*$regexp\s*\((?:\s*\([^)]+\))?\s*\2\s*\)\s*;\s*\}))/sxg)
128 129
            {
              $found_match = 1;
130 131 132
              $list
                and (print "$file\0"), next FILE;
              print "$file: $1\n";
133 134
            }
        }
135 136 137
    }
  continue
    {
138 139
      close FH;
    }
140 141 142 143 144

  $found_match && $err == EXIT_NO_MATCH
    and $err = EXIT_MATCH;

  exit $err;
145 146 147 148 149 150
}

my $foo = <<'EOF';
# The above is to *find* them.
# This adjusts them, removing the unnecessary "if (p)" part.

151 152 153
# FIXME: do something like this as an option (doesn't do braces):
git ls-files -z |xargs -0 \
perl -0x3b -pi -e 's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*NULL)?\s*\)\s+(k?free\s*\(\s*\1\s*\))/$2/s'
154

155 156 157
useless-if-before-free -l $(lid -knone free) | xargs -0 \
  perl -0x3b -pi -e \
   's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*NULL)?\s*\)\s+(free\s*\(\s*\1\s*\))/$2/s'
158

159 160 161 162
Be careful that the result of the above transformation is valid.
If the matched string is followed by "else", then obviously, it won't be.

When modifying files, refuse to process anything other than a regular file.
163 164 165 166 167 168 169 170 171 172
EOF

## Local Variables:
## indent-tabs-mode: nil
## eval: (add-hook 'write-file-hooks 'time-stamp)
## time-stamp-start: "my $VERSION = '"
## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
## time-stamp-time-zone: "UTC"
## time-stamp-end: "'; # UTC"
## End: