Home | History | Annotate | Download | only in build-aux
      1 eval '(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 
      7 my $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 
     30 use strict;
     31 use warnings;
     32 use Getopt::Long;
     33 
     34 (my $ME = $0) =~ s|.*/||;
     35 
     36 # use File::Coda; # http://meyering.net/code/Coda/
     37 END {
     38   defined fileno STDOUT or return;
     39   close STDOUT and return;
     40   warn "$ME: failed to close standard output: $!\n";
     41   $? ||= 1;
     42 }
     43 
     44 sub 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;
     55 Usage: $ME [OPTIONS] FILE...
     56 
     57 Detect 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
     59 the semantics of the C code in FILE.  Use --name=FOO --name=BAR to also
     60 detect free-like functions named FOO and BAR.
     61 
     62 OPTIONS:
     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 
     71 Exit status:
     72 
     73   0   one or more matches
     74   1   no match
     75   2   an error
     76 
     77 EXAMPLE:
     78 
     79 For example, this command prints all removable "if" tests before "free"
     80 and "kfree" calls in the linux kernel sources:
     81 
     82     git ls-files -z |xargs -0 $ME --name=kfree
     83 
     84 EOF
     85     }
     86   exit $exit_code;
     87 }
     88 
     89 sub 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 
    173 my $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):
    178 free=xfree
    179 git 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...
    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 
    193 Be careful that the result of the above transformation is valid.
    194 If the matched string is followed by "else", then obviously, it won't be.
    195 
    196 When modifying files, refuse to process anything other than a regular file.
    197 EOF
    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