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

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