Blame build-aux/useless-if-before-free

Packit Service 991b93
#!/bin/sh
Packit Service 991b93
#! -*-perl-*-
Packit Service 991b93
Packit aea12f
# Detect instances of "if (p) free (p);".
Packit aea12f
# Likewise "if (p != 0)", "if (0 != p)", or with NULL; and with braces.
Packit aea12f
Packit Service 991b93
# Copyright (C) 2008-2020 Free Software Foundation, Inc.
Packit Service 991b93
#
Packit aea12f
# This program is free software: you can redistribute it and/or modify
Packit aea12f
# it under the terms of the GNU General Public License as published by
Packit aea12f
# the Free Software Foundation, either version 3 of the License, or
Packit aea12f
# (at your option) any later version.
Packit Service 991b93
#
Packit aea12f
# This program is distributed in the hope that it will be useful,
Packit aea12f
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit aea12f
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit aea12f
# GNU General Public License for more details.
Packit Service 991b93
#
Packit aea12f
# You should have received a copy of the GNU General Public License
Packit aea12f
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
Packit Service 991b93
#
Packit aea12f
# Written by Jim Meyering
Packit aea12f
Packit Service 991b93
# This is a prologue that allows to run a perl script as an executable
Packit Service 991b93
# on systems that are compliant to a POSIX version before POSIX:2017.
Packit Service 991b93
# On such systems, the usual invocation of an executable through execlp()
Packit Service 991b93
# or execvp() fails with ENOEXEC if it is a script that does not start
Packit Service 991b93
# with a #! line.  The script interpreter mentioned in the #! line has
Packit Service 991b93
# to be /bin/sh, because on GuixSD systems that is the only program that
Packit Service 991b93
# has a fixed file name.  The second line is essential for perl and is
Packit Service 991b93
# also useful for editing this file in Emacs.  The next two lines below
Packit Service 991b93
# are valid code in both sh and perl.  When executed by sh, they re-execute
Packit Service 991b93
# the script through the perl program found in $PATH.  The '-x' option
Packit Service 991b93
# is essential as well; without it, perl would re-execute the script
Packit Service 991b93
# through /bin/sh.  When executed by perl, the next two lines are a no-op.
Packit Service 991b93
eval 'exec perl -wSx "$0" "$@"'
Packit Service 991b93
     if 0;
Packit Service 991b93
Packit Service 991b93
my $VERSION = '2020-04-04 15:07'; # UTC
Packit Service 991b93
# The definition above must lie within the first 8 lines in order
Packit Service 991b93
# for the Emacs time-stamp write hook (at end) to update it.
Packit Service 991b93
# If you change this file with Emacs, please let the write hook
Packit Service 991b93
# do its job.  Otherwise, update this string manually.
Packit Service 991b93
Packit aea12f
use strict;
Packit aea12f
use warnings;
Packit aea12f
use Getopt::Long;
Packit aea12f
Packit aea12f
(my $ME = $0) =~ s|.*/||;
Packit aea12f
Packit aea12f
# use File::Coda; # https://meyering.net/code/Coda/
Packit aea12f
END {
Packit aea12f
  defined fileno STDOUT or return;
Packit aea12f
  close STDOUT and return;
Packit aea12f
  warn "$ME: failed to close standard output: $!\n";
Packit aea12f
  $? ||= 1;
Packit aea12f
}
Packit aea12f
Packit aea12f
sub usage ($)
Packit aea12f
{
Packit aea12f
  my ($exit_code) = @_;
Packit aea12f
  my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
Packit aea12f
  if ($exit_code != 0)
Packit aea12f
    {
Packit aea12f
      print $STREAM "Try '$ME --help' for more information.\n";
Packit aea12f
    }
Packit aea12f
  else
Packit aea12f
    {
Packit aea12f
      print $STREAM <
Packit aea12f
Usage: $ME [OPTIONS] FILE...
Packit aea12f
Packit aea12f
Detect any instance in FILE of a useless "if" test before a free call, e.g.,
Packit aea12f
"if (p) free (p);".  Any such test may be safely removed without affecting
Packit aea12f
the semantics of the C code in FILE.  Use --name=FOO --name=BAR to also
Packit aea12f
detect free-like functions named FOO and BAR.
Packit aea12f
Packit aea12f
OPTIONS:
Packit aea12f
Packit aea12f
   --list       print only the name of each matching FILE (\\0-terminated)
Packit aea12f
   --name=N     add name N to the list of \'free\'-like functions to detect;
Packit aea12f
                  may be repeated
Packit aea12f
Packit aea12f
   --help       display this help and exit
Packit aea12f
   --version    output version information and exit
Packit aea12f
Packit aea12f
Exit status:
Packit aea12f
Packit aea12f
  0   one or more matches
Packit aea12f
  1   no match
Packit aea12f
  2   an error
Packit aea12f
Packit aea12f
EXAMPLE:
Packit aea12f
Packit aea12f
For example, this command prints all removable "if" tests before "free"
Packit aea12f
and "kfree" calls in the linux kernel sources:
Packit aea12f
Packit aea12f
    git ls-files -z |xargs -0 $ME --name=kfree
Packit aea12f
Packit aea12f
EOF
Packit aea12f
    }
Packit aea12f
  exit $exit_code;
Packit aea12f
}
Packit aea12f
Packit aea12f
sub is_NULL ($)
Packit aea12f
{
Packit aea12f
  my ($expr) = @_;
Packit aea12f
  return ($expr eq 'NULL' || $expr eq '0');
Packit aea12f
}
Packit aea12f
Packit aea12f
{
Packit aea12f
  sub EXIT_MATCH {0}
Packit aea12f
  sub EXIT_NO_MATCH {1}
Packit aea12f
  sub EXIT_ERROR {2}
Packit aea12f
  my $err = EXIT_NO_MATCH;
Packit aea12f
Packit aea12f
  my $list;
Packit aea12f
  my @name = qw(free);
Packit aea12f
  GetOptions
Packit aea12f
    (
Packit aea12f
     help => sub { usage 0 },
Packit aea12f
     version => sub { print "$ME version $VERSION\n"; exit },
Packit aea12f
     list => \$list,
Packit aea12f
     'name=s@' => \@name,
Packit aea12f
    ) or usage 1;
Packit aea12f
Packit aea12f
  # Make sure we have the right number of non-option arguments.
Packit aea12f
  # Always tell the user why we fail.
Packit aea12f
  @ARGV < 1
Packit aea12f
    and (warn "$ME: missing FILE argument\n"), usage EXIT_ERROR;
Packit aea12f
Packit aea12f
  my $or = join '|', @name;
Packit aea12f
  my $regexp = qr/(?:$or)/;
Packit aea12f
Packit aea12f
  # Set the input record separator.
Packit aea12f
  # Note: this makes it impractical to print line numbers.
Packit aea12f
  $/ = '"';
Packit aea12f
Packit aea12f
  my $found_match = 0;
Packit aea12f
 FILE:
Packit aea12f
  foreach my $file (@ARGV)
Packit aea12f
    {
Packit aea12f
      open FH, '<', $file
Packit aea12f
        or (warn "$ME: can't open '$file' for reading: $!\n"),
Packit aea12f
          $err = EXIT_ERROR, next;
Packit aea12f
      while (defined (my $line = <FH>))
Packit aea12f
        {
Packit aea12f
          # Skip non-matching lines early to save time
Packit aea12f
          $line =~ /\bif\b/
Packit aea12f
            or next;
Packit aea12f
          while ($line =~
Packit aea12f
              /\b(if\s*\(\s*([^)]+?)(?:\s*!=\s*([^)]+?))?\s*\)
Packit aea12f
              #  1          2                  3
Packit aea12f
               (?:   \s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)\s*;|
Packit aea12f
                \s*\{\s*$regexp\s*\((?:\s*\([^)]+\))?\s*([^)]+)\)\s*;\s*\}))/sxg)
Packit aea12f
            {
Packit aea12f
              my $all = $1;
Packit aea12f
              my ($lhs, $rhs) = ($2, $3);
Packit aea12f
              my ($free_opnd, $braced_free_opnd) = ($4, $5);
Packit aea12f
              my $non_NULL;
Packit aea12f
              if (!defined $rhs) { $non_NULL = $lhs }
Packit aea12f
              elsif (is_NULL $rhs) { $non_NULL = $lhs }
Packit aea12f
              elsif (is_NULL $lhs) { $non_NULL = $rhs }
Packit aea12f
              else { next }
Packit aea12f
Packit aea12f
              # Compare the non-NULL part of the "if" expression and the
Packit aea12f
              # free'd expression, without regard to white space.
Packit aea12f
              $non_NULL =~ tr/ \t//d;
Packit aea12f
              my $e2 = defined $free_opnd ? $free_opnd : $braced_free_opnd;
Packit aea12f
              $e2 =~ tr/ \t//d;
Packit aea12f
              if ($non_NULL eq $e2)
Packit aea12f
                {
Packit aea12f
                  $found_match = 1;
Packit aea12f
                  $list
Packit aea12f
                    and (print "$file\0"), next FILE;
Packit aea12f
                  print "$file: $all\n";
Packit aea12f
                }
Packit aea12f
            }
Packit aea12f
        }
Packit aea12f
    }
Packit aea12f
  continue
Packit aea12f
    {
Packit aea12f
      close FH;
Packit aea12f
    }
Packit aea12f
Packit aea12f
  $found_match && $err == EXIT_NO_MATCH
Packit aea12f
    and $err = EXIT_MATCH;
Packit aea12f
Packit aea12f
  exit $err;
Packit aea12f
}
Packit aea12f
Packit aea12f
my $foo = <<'EOF';
Packit aea12f
# The above is to *find* them.
Packit aea12f
# This adjusts them, removing the unnecessary "if (p)" part.
Packit aea12f
Packit aea12f
# FIXME: do something like this as an option (doesn't do braces):
Packit aea12f
free=xfree
Packit aea12f
git grep -l -z "$free *(" \
Packit aea12f
  | xargs -0 useless-if-before-free -l --name="$free" \
Packit aea12f
  | xargs -0 perl -0x3b -pi -e \
Packit aea12f
   's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s+('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\)\s*;)/$2/s'
Packit aea12f
Packit aea12f
# Use the following to remove redundant uses of kfree inside braces.
Packit aea12f
# Note that -0777 puts perl in slurp-whole-file mode;
Packit aea12f
# but we have plenty of memory, these days...
Packit aea12f
free=kfree
Packit aea12f
git grep -l -z "$free *(" \
Packit aea12f
  | xargs -0 useless-if-before-free -l --name="$free" \
Packit aea12f
  | xargs -0 perl -0777 -pi -e \
Packit aea12f
     's/\bif\s*\(\s*(\S+?)(?:\s*!=\s*(?:0|NULL))?\s*\)\s*\{\s*('"$free"'\s*\((?:\s*\([^)]+\))?\s*\1\s*\);)\s*\}[^\n]*$/$2/gms'
Packit aea12f
Packit aea12f
Be careful that the result of the above transformation is valid.
Packit aea12f
If the matched string is followed by "else", then obviously, it won't be.
Packit aea12f
Packit aea12f
When modifying files, refuse to process anything other than a regular file.
Packit aea12f
EOF
Packit aea12f
Packit aea12f
## Local Variables:
Packit aea12f
## mode: perl
Packit aea12f
## indent-tabs-mode: nil
Packit aea12f
## eval: (add-hook 'before-save-hook 'time-stamp)
Packit Service 991b93
## time-stamp-line-limit: 50
Packit aea12f
## time-stamp-start: "my $VERSION = '"
Packit aea12f
## time-stamp-format: "%:y-%02m-%02d %02H:%02M"
Packit aea12f
## time-stamp-time-zone: "UTC0"
Packit aea12f
## time-stamp-end: "'; # UTC"
Packit aea12f
## End: