1b7d5a9c2SKamil Rytarowski#!/usr/bin/env perl
22dbc4ebcSMarkus Armbruster#
32dbc4ebcSMarkus Armbruster# Clean up include guards in headers
42dbc4ebcSMarkus Armbruster#
52dbc4ebcSMarkus Armbruster# Copyright (C) 2016 Red Hat, Inc.
62dbc4ebcSMarkus Armbruster#
72dbc4ebcSMarkus Armbruster# Authors:
82dbc4ebcSMarkus Armbruster#  Markus Armbruster <armbru@redhat.com>
92dbc4ebcSMarkus Armbruster#
102dbc4ebcSMarkus Armbruster# This work is licensed under the terms of the GNU GPL, version 2 or
112dbc4ebcSMarkus Armbruster# (at your option) any later version. See the COPYING file in the
122dbc4ebcSMarkus Armbruster# top-level directory.
132dbc4ebcSMarkus Armbruster#
142dbc4ebcSMarkus Armbruster# Usage: scripts/clean-header-guards.pl [OPTION]... [FILE]...
152dbc4ebcSMarkus Armbruster#     -c CC     Use a compiler other than cc
162dbc4ebcSMarkus Armbruster#     -n        Suppress actual cleanup
172dbc4ebcSMarkus Armbruster#     -v        Show which files are cleaned up, and which are skipped
182dbc4ebcSMarkus Armbruster#
192dbc4ebcSMarkus Armbruster# Does the following:
202dbc4ebcSMarkus Armbruster# - Header files without a recognizable header guard are skipped.
212dbc4ebcSMarkus Armbruster# - Clean up any untidy header guards in-place.  Warn if the cleanup
2265fdb3ccSzhaolichang#   renames guard symbols, and explain how to find occurrences of these
232dbc4ebcSMarkus Armbruster#   symbols that may have to be updated manually.
242dbc4ebcSMarkus Armbruster# - Warn about duplicate header guard symbols.  To make full use of
252dbc4ebcSMarkus Armbruster#   this warning, you should clean up *all* headers in one run.
262dbc4ebcSMarkus Armbruster# - Warn when preprocessing a header with its guard symbol defined
272dbc4ebcSMarkus Armbruster#   produces anything but whitespace.  The preprocessor is run like
282dbc4ebcSMarkus Armbruster#   "cc -E -DGUARD_H -c -P -", and fed the test program on stdin.
292dbc4ebcSMarkus Armbruster
302dbc4ebcSMarkus Armbrusteruse strict;
31b7d5a9c2SKamil Rytarowskiuse warnings;
322dbc4ebcSMarkus Armbrusteruse Getopt::Std;
332dbc4ebcSMarkus Armbruster
342dbc4ebcSMarkus Armbruster# Stuff we don't want to clean because we import it into our tree:
35*21166502SThomas Huthmy $exclude = qr,^(include/standard-headers/|linux-headers/
36*21166502SThomas Huth    |pc-bios/|tests/tcg/|tests/multiboot/),x;
372dbc4ebcSMarkus Armbruster# Stuff that is expected to fail the preprocessing test:
382dbc4ebcSMarkus Armbrustermy $exclude_cpp = qr,^include/libdecnumber/decNumberLocal.h,;
392dbc4ebcSMarkus Armbruster
402dbc4ebcSMarkus Armbrustermy %guarded = ();
412dbc4ebcSMarkus Armbrustermy %old_guard = ();
422dbc4ebcSMarkus Armbruster
432dbc4ebcSMarkus Armbrusterour $opt_c = "cc";
442dbc4ebcSMarkus Armbrusterour $opt_n = 0;
452dbc4ebcSMarkus Armbrusterour $opt_v = 0;
462dbc4ebcSMarkus Armbrustergetopts("c:nv");
472dbc4ebcSMarkus Armbruster
482dbc4ebcSMarkus Armbrustersub skipping {
492dbc4ebcSMarkus Armbruster    my ($fname, $msg, $line1, $line2) = @_;
502dbc4ebcSMarkus Armbruster
512dbc4ebcSMarkus Armbruster    return if !$opt_v or $fname =~ $exclude;
522dbc4ebcSMarkus Armbruster    print "$fname skipped: $msg\n";
532dbc4ebcSMarkus Armbruster    print "    $line1" if defined $line1;
542dbc4ebcSMarkus Armbruster    print "    $line2" if defined $line2;
552dbc4ebcSMarkus Armbruster}
562dbc4ebcSMarkus Armbruster
572dbc4ebcSMarkus Armbrustersub gripe {
582dbc4ebcSMarkus Armbruster    my ($fname, $msg) = @_;
592dbc4ebcSMarkus Armbruster    return if $fname =~ $exclude;
602dbc4ebcSMarkus Armbruster    print STDERR "$fname: warning: $msg\n";
612dbc4ebcSMarkus Armbruster}
622dbc4ebcSMarkus Armbruster
632dbc4ebcSMarkus Armbrustersub slurp {
642dbc4ebcSMarkus Armbruster    my ($fname) = @_;
652dbc4ebcSMarkus Armbruster    local $/;                   # slurp
662dbc4ebcSMarkus Armbruster    open(my $in, "<", $fname)
672dbc4ebcSMarkus Armbruster        or die "can't open $fname for reading: $!";
682dbc4ebcSMarkus Armbruster    return <$in>;
692dbc4ebcSMarkus Armbruster}
702dbc4ebcSMarkus Armbruster
712dbc4ebcSMarkus Armbrustersub unslurp {
722dbc4ebcSMarkus Armbruster    my ($fname, $contents) = @_;
732dbc4ebcSMarkus Armbruster    open (my $out, ">", $fname)
742dbc4ebcSMarkus Armbruster        or die "can't open $fname for writing: $!";
752dbc4ebcSMarkus Armbruster    print $out $contents
762dbc4ebcSMarkus Armbruster        or die "error writing $fname: $!";
772dbc4ebcSMarkus Armbruster    close $out
782dbc4ebcSMarkus Armbruster        or die "error writing $fname: $!";
792dbc4ebcSMarkus Armbruster}
802dbc4ebcSMarkus Armbruster
812dbc4ebcSMarkus Armbrustersub fname2guard {
822dbc4ebcSMarkus Armbruster    my ($fname) = @_;
832dbc4ebcSMarkus Armbruster    $fname =~ tr/a-z/A-Z/;
842dbc4ebcSMarkus Armbruster    $fname =~ tr/A-Z0-9/_/cs;
852dbc4ebcSMarkus Armbruster    return $fname;
862dbc4ebcSMarkus Armbruster}
872dbc4ebcSMarkus Armbruster
882dbc4ebcSMarkus Armbrustersub preprocess {
892dbc4ebcSMarkus Armbruster    my ($fname, $guard) = @_;
902dbc4ebcSMarkus Armbruster
912dbc4ebcSMarkus Armbruster    open(my $pipe, "-|", "$opt_c -E -D$guard -c -P - <$fname")
922dbc4ebcSMarkus Armbruster        or die "can't run $opt_c: $!";
932dbc4ebcSMarkus Armbruster    while (<$pipe>) {
942dbc4ebcSMarkus Armbruster        if ($_ =~ /\S/) {
952dbc4ebcSMarkus Armbruster            gripe($fname, "not blank after preprocessing");
962dbc4ebcSMarkus Armbruster            last;
972dbc4ebcSMarkus Armbruster        }
982dbc4ebcSMarkus Armbruster    }
992dbc4ebcSMarkus Armbruster    close $pipe
1002dbc4ebcSMarkus Armbruster        or gripe($fname, "preprocessing failed ($opt_c exit status $?)");
1012dbc4ebcSMarkus Armbruster}
1022dbc4ebcSMarkus Armbruster
1032dbc4ebcSMarkus Armbrusterfor my $fname (@ARGV) {
1042dbc4ebcSMarkus Armbruster    my $text = slurp($fname);
1052dbc4ebcSMarkus Armbruster
106c0a9956bSMarkus Armbruster    $text =~ m,\A(\s*\n|\s*//\N*\n|\s*/\*.*?\*/\s*\n)*|,sg;
1072dbc4ebcSMarkus Armbruster    my $pre = $&;
1082dbc4ebcSMarkus Armbruster    unless ($text =~ /\G(.*\n)/g) {
1092dbc4ebcSMarkus Armbruster        $text =~ /\G.*/;
1102dbc4ebcSMarkus Armbruster        skipping($fname, "no recognizable header guard", "$&\n");
1112dbc4ebcSMarkus Armbruster        next;
1122dbc4ebcSMarkus Armbruster    }
1132dbc4ebcSMarkus Armbruster    my $line1 = $1;
1142dbc4ebcSMarkus Armbruster    unless ($text =~ /\G(.*\n)/g) {
1152dbc4ebcSMarkus Armbruster        $text =~ /\G.*/;
1162dbc4ebcSMarkus Armbruster        skipping($fname, "no recognizable header guard", "$&\n");
1172dbc4ebcSMarkus Armbruster        next;
1182dbc4ebcSMarkus Armbruster    }
1192dbc4ebcSMarkus Armbruster    my $line2 = $1;
1202dbc4ebcSMarkus Armbruster    my $body = substr($text, pos($text));
1212dbc4ebcSMarkus Armbruster
1222dbc4ebcSMarkus Armbruster    unless ($line1 =~ /^\s*\#\s*(if\s*\!\s*defined(\s*\()?|ifndef)\s*
1232dbc4ebcSMarkus Armbruster                       ([A-Za-z0-9_]+)/x) {
1242dbc4ebcSMarkus Armbruster        skipping($fname, "no recognizable header guard", $line1, $line2);
1252dbc4ebcSMarkus Armbruster        next;
1262dbc4ebcSMarkus Armbruster    }
1272dbc4ebcSMarkus Armbruster    my $guard = $3;
1282dbc4ebcSMarkus Armbruster    unless ($line2 =~ /^\s*\#\s*define\s+([A-Za-z0-9_]+)/) {
1292dbc4ebcSMarkus Armbruster        skipping($fname, "no recognizable header guard", $line1, $line2);
1302dbc4ebcSMarkus Armbruster        next;
1312dbc4ebcSMarkus Armbruster    }
1322dbc4ebcSMarkus Armbruster    my $guard2 = $1;
1332dbc4ebcSMarkus Armbruster    unless ($guard2 eq $guard) {
1342dbc4ebcSMarkus Armbruster        skipping($fname, "mismatched header guard ($guard vs. $guard2) ",
1352dbc4ebcSMarkus Armbruster                 $line1, $line2);
1362dbc4ebcSMarkus Armbruster        next;
1372dbc4ebcSMarkus Armbruster    }
1382dbc4ebcSMarkus Armbruster
1392dbc4ebcSMarkus Armbruster    unless ($body =~ m,\A((.*\n)*)
140c0a9956bSMarkus Armbruster                       ([ \t]*\#[ \t]*endif([ \t]*\N*)\n)
141c0a9956bSMarkus Armbruster                       ((?s)(\s*\n|\s*//\N*\n|\s*/\*.*?\*/\s*\n)*)
142c0a9956bSMarkus Armbruster                       \Z,x) {
1432dbc4ebcSMarkus Armbruster        skipping($fname, "can't find end of header guard");
1442dbc4ebcSMarkus Armbruster        next;
1452dbc4ebcSMarkus Armbruster    }
1462dbc4ebcSMarkus Armbruster    $body = $1;
1472dbc4ebcSMarkus Armbruster    my $line3 = $3;
1482dbc4ebcSMarkus Armbruster    my $endif_comment = $4;
149c0a9956bSMarkus Armbruster    my $post = $5;
1502dbc4ebcSMarkus Armbruster
1512dbc4ebcSMarkus Armbruster    my $oldg = $guard;
1522dbc4ebcSMarkus Armbruster
1532dbc4ebcSMarkus Armbruster    unless ($fname =~ $exclude) {
1542dbc4ebcSMarkus Armbruster        my @issues = ();
1552dbc4ebcSMarkus Armbruster        $guard =~ tr/a-z/A-Z/
1562dbc4ebcSMarkus Armbruster            and push @issues, "contains lowercase letters";
1572dbc4ebcSMarkus Armbruster        $guard =~ s/^_+//
1582dbc4ebcSMarkus Armbruster            and push @issues, "is a reserved identifier";
1592dbc4ebcSMarkus Armbruster        $guard =~ s/(_H)?_*$/_H/
1602dbc4ebcSMarkus Armbruster            and $& ne "_H" and push @issues, "doesn't end with _H";
1612dbc4ebcSMarkus Armbruster        unless ($guard =~ /^[A-Z][A-Z0-9_]*_H/) {
1622dbc4ebcSMarkus Armbruster            skipping($fname, "can't clean up odd guard symbol $oldg\n",
1632dbc4ebcSMarkus Armbruster                     $line1, $line2);
1642dbc4ebcSMarkus Armbruster            next;
1652dbc4ebcSMarkus Armbruster        }
1662dbc4ebcSMarkus Armbruster
1672dbc4ebcSMarkus Armbruster        my $exp = fname2guard($fname =~ s,.*/,,r);
1682dbc4ebcSMarkus Armbruster        unless ($guard =~ /\Q$exp\E\Z/) {
1692dbc4ebcSMarkus Armbruster            $guard = fname2guard($fname =~ s,^include/,,r);
1702dbc4ebcSMarkus Armbruster            push @issues, "doesn't match the file name";
1712dbc4ebcSMarkus Armbruster        }
1722dbc4ebcSMarkus Armbruster        if (@issues and $opt_v) {
1732dbc4ebcSMarkus Armbruster            print "$fname guard $oldg needs cleanup:\n    ",
1742dbc4ebcSMarkus Armbruster                join(", ", @issues), "\n";
1752dbc4ebcSMarkus Armbruster        }
1762dbc4ebcSMarkus Armbruster    }
1772dbc4ebcSMarkus Armbruster
1782dbc4ebcSMarkus Armbruster    $old_guard{$guard} = $oldg
1792dbc4ebcSMarkus Armbruster        if $guard ne $oldg;
1802dbc4ebcSMarkus Armbruster
1812dbc4ebcSMarkus Armbruster    if (exists $guarded{$guard}) {
1822dbc4ebcSMarkus Armbruster        gripe($fname, "guard $guard also used by $guarded{$guard}");
1832dbc4ebcSMarkus Armbruster    } else {
1842dbc4ebcSMarkus Armbruster        $guarded{$guard} = $fname;
1852dbc4ebcSMarkus Armbruster    }
1862dbc4ebcSMarkus Armbruster
1872dbc4ebcSMarkus Armbruster    unless ($fname =~ $exclude) {
1882dbc4ebcSMarkus Armbruster        my $newl1 = "#ifndef $guard\n";
1892dbc4ebcSMarkus Armbruster        my $newl2 = "#define $guard\n";
1902dbc4ebcSMarkus Armbruster        my $newl3 = "#endif\n";
191c0a9956bSMarkus Armbruster        $newl3 =~ s,\Z, /* $guard */, if $endif_comment;
1922dbc4ebcSMarkus Armbruster        if ($line1 ne $newl1 or $line2 ne $newl2 or $line3 ne $newl3) {
1932dbc4ebcSMarkus Armbruster            $pre =~ s/\n*\Z/\n\n/ if $pre =~ /\N/;
1942dbc4ebcSMarkus Armbruster            $body =~ s/\A\n*/\n/;
1952dbc4ebcSMarkus Armbruster            if ($opt_n) {
1962dbc4ebcSMarkus Armbruster                print "$fname would be cleaned up\n" if $opt_v;
1972dbc4ebcSMarkus Armbruster            } else {
198c0a9956bSMarkus Armbruster                unslurp($fname, "$pre$newl1$newl2$body$newl3$post");
1992dbc4ebcSMarkus Armbruster                print "$fname cleaned up\n" if $opt_v;
2002dbc4ebcSMarkus Armbruster            }
2012dbc4ebcSMarkus Armbruster        }
2022dbc4ebcSMarkus Armbruster    }
2032dbc4ebcSMarkus Armbruster
2042dbc4ebcSMarkus Armbruster    preprocess($fname, $opt_n ? $oldg : $guard)
2052dbc4ebcSMarkus Armbruster        unless $fname =~ $exclude or $fname =~ $exclude_cpp;
2062dbc4ebcSMarkus Armbruster}
2072dbc4ebcSMarkus Armbruster
2082dbc4ebcSMarkus Armbrusterif (%old_guard) {
2092dbc4ebcSMarkus Armbruster    print STDERR "warning: guard symbol renaming may break things\n";
2102dbc4ebcSMarkus Armbruster    for my $guard (sort keys %old_guard) {
2112dbc4ebcSMarkus Armbruster        print STDERR "    $old_guard{$guard} -> $guard\n";
2122dbc4ebcSMarkus Armbruster    }
2132dbc4ebcSMarkus Armbruster    print STDERR "To find uses that may have to be updated try:\n";
2142dbc4ebcSMarkus Armbruster    print STDERR "    git grep -Ew '", join("|", sort values %old_guard),
2152dbc4ebcSMarkus Armbruster        "'\n";
2162dbc4ebcSMarkus Armbruster}
217