1*2dbc4ebcSMarkus Armbruster#!/usr/bin/perl -w
2*2dbc4ebcSMarkus Armbruster#
3*2dbc4ebcSMarkus Armbruster# Clean up include guards in headers
4*2dbc4ebcSMarkus Armbruster#
5*2dbc4ebcSMarkus Armbruster# Copyright (C) 2016 Red Hat, Inc.
6*2dbc4ebcSMarkus Armbruster#
7*2dbc4ebcSMarkus Armbruster# Authors:
8*2dbc4ebcSMarkus Armbruster#  Markus Armbruster <armbru@redhat.com>
9*2dbc4ebcSMarkus Armbruster#
10*2dbc4ebcSMarkus Armbruster# This work is licensed under the terms of the GNU GPL, version 2 or
11*2dbc4ebcSMarkus Armbruster# (at your option) any later version. See the COPYING file in the
12*2dbc4ebcSMarkus Armbruster# top-level directory.
13*2dbc4ebcSMarkus Armbruster#
14*2dbc4ebcSMarkus Armbruster# Usage: scripts/clean-header-guards.pl [OPTION]... [FILE]...
15*2dbc4ebcSMarkus Armbruster#     -c CC     Use a compiler other than cc
16*2dbc4ebcSMarkus Armbruster#     -n        Suppress actual cleanup
17*2dbc4ebcSMarkus Armbruster#     -v        Show which files are cleaned up, and which are skipped
18*2dbc4ebcSMarkus Armbruster#
19*2dbc4ebcSMarkus Armbruster# Does the following:
20*2dbc4ebcSMarkus Armbruster# - Header files without a recognizable header guard are skipped.
21*2dbc4ebcSMarkus Armbruster# - Clean up any untidy header guards in-place.  Warn if the cleanup
22*2dbc4ebcSMarkus Armbruster#   renames guard symbols, and explain how to find occurences of these
23*2dbc4ebcSMarkus Armbruster#   symbols that may have to be updated manually.
24*2dbc4ebcSMarkus Armbruster# - Warn about duplicate header guard symbols.  To make full use of
25*2dbc4ebcSMarkus Armbruster#   this warning, you should clean up *all* headers in one run.
26*2dbc4ebcSMarkus Armbruster# - Warn when preprocessing a header with its guard symbol defined
27*2dbc4ebcSMarkus Armbruster#   produces anything but whitespace.  The preprocessor is run like
28*2dbc4ebcSMarkus Armbruster#   "cc -E -DGUARD_H -c -P -", and fed the test program on stdin.
29*2dbc4ebcSMarkus Armbruster
30*2dbc4ebcSMarkus Armbrusteruse strict;
31*2dbc4ebcSMarkus Armbrusteruse Getopt::Std;
32*2dbc4ebcSMarkus Armbruster
33*2dbc4ebcSMarkus Armbruster# Stuff we don't want to clean because we import it into our tree:
34*2dbc4ebcSMarkus Armbrustermy $exclude = qr,^(disas/libvixl/|include/standard-headers/
35*2dbc4ebcSMarkus Armbruster    |linux-headers/|pc-bios/|tests/tcg/|tests/multiboot/),x;
36*2dbc4ebcSMarkus Armbruster# Stuff that is expected to fail the preprocessing test:
37*2dbc4ebcSMarkus Armbrustermy $exclude_cpp = qr,^include/libdecnumber/decNumberLocal.h,;
38*2dbc4ebcSMarkus Armbruster
39*2dbc4ebcSMarkus Armbrustermy %guarded = ();
40*2dbc4ebcSMarkus Armbrustermy %old_guard = ();
41*2dbc4ebcSMarkus Armbruster
42*2dbc4ebcSMarkus Armbrusterour $opt_c = "cc";
43*2dbc4ebcSMarkus Armbrusterour $opt_n = 0;
44*2dbc4ebcSMarkus Armbrusterour $opt_v = 0;
45*2dbc4ebcSMarkus Armbrustergetopts("c:nv");
46*2dbc4ebcSMarkus Armbruster
47*2dbc4ebcSMarkus Armbrustersub skipping {
48*2dbc4ebcSMarkus Armbruster    my ($fname, $msg, $line1, $line2) = @_;
49*2dbc4ebcSMarkus Armbruster
50*2dbc4ebcSMarkus Armbruster    return if !$opt_v or $fname =~ $exclude;
51*2dbc4ebcSMarkus Armbruster    print "$fname skipped: $msg\n";
52*2dbc4ebcSMarkus Armbruster    print "    $line1" if defined $line1;
53*2dbc4ebcSMarkus Armbruster    print "    $line2" if defined $line2;
54*2dbc4ebcSMarkus Armbruster}
55*2dbc4ebcSMarkus Armbruster
56*2dbc4ebcSMarkus Armbrustersub gripe {
57*2dbc4ebcSMarkus Armbruster    my ($fname, $msg) = @_;
58*2dbc4ebcSMarkus Armbruster    return if $fname =~ $exclude;
59*2dbc4ebcSMarkus Armbruster    print STDERR "$fname: warning: $msg\n";
60*2dbc4ebcSMarkus Armbruster}
61*2dbc4ebcSMarkus Armbruster
62*2dbc4ebcSMarkus Armbrustersub slurp {
63*2dbc4ebcSMarkus Armbruster    my ($fname) = @_;
64*2dbc4ebcSMarkus Armbruster    local $/;                   # slurp
65*2dbc4ebcSMarkus Armbruster    open(my $in, "<", $fname)
66*2dbc4ebcSMarkus Armbruster        or die "can't open $fname for reading: $!";
67*2dbc4ebcSMarkus Armbruster    return <$in>;
68*2dbc4ebcSMarkus Armbruster}
69*2dbc4ebcSMarkus Armbruster
70*2dbc4ebcSMarkus Armbrustersub unslurp {
71*2dbc4ebcSMarkus Armbruster    my ($fname, $contents) = @_;
72*2dbc4ebcSMarkus Armbruster    open (my $out, ">", $fname)
73*2dbc4ebcSMarkus Armbruster        or die "can't open $fname for writing: $!";
74*2dbc4ebcSMarkus Armbruster    print $out $contents
75*2dbc4ebcSMarkus Armbruster        or die "error writing $fname: $!";
76*2dbc4ebcSMarkus Armbruster    close $out
77*2dbc4ebcSMarkus Armbruster        or die "error writing $fname: $!";
78*2dbc4ebcSMarkus Armbruster}
79*2dbc4ebcSMarkus Armbruster
80*2dbc4ebcSMarkus Armbrustersub fname2guard {
81*2dbc4ebcSMarkus Armbruster    my ($fname) = @_;
82*2dbc4ebcSMarkus Armbruster    $fname =~ tr/a-z/A-Z/;
83*2dbc4ebcSMarkus Armbruster    $fname =~ tr/A-Z0-9/_/cs;
84*2dbc4ebcSMarkus Armbruster    return $fname;
85*2dbc4ebcSMarkus Armbruster}
86*2dbc4ebcSMarkus Armbruster
87*2dbc4ebcSMarkus Armbrustersub preprocess {
88*2dbc4ebcSMarkus Armbruster    my ($fname, $guard) = @_;
89*2dbc4ebcSMarkus Armbruster
90*2dbc4ebcSMarkus Armbruster    open(my $pipe, "-|", "$opt_c -E -D$guard -c -P - <$fname")
91*2dbc4ebcSMarkus Armbruster        or die "can't run $opt_c: $!";
92*2dbc4ebcSMarkus Armbruster    while (<$pipe>) {
93*2dbc4ebcSMarkus Armbruster        if ($_ =~ /\S/) {
94*2dbc4ebcSMarkus Armbruster            gripe($fname, "not blank after preprocessing");
95*2dbc4ebcSMarkus Armbruster            last;
96*2dbc4ebcSMarkus Armbruster        }
97*2dbc4ebcSMarkus Armbruster    }
98*2dbc4ebcSMarkus Armbruster    close $pipe
99*2dbc4ebcSMarkus Armbruster        or gripe($fname, "preprocessing failed ($opt_c exit status $?)");
100*2dbc4ebcSMarkus Armbruster}
101*2dbc4ebcSMarkus Armbruster
102*2dbc4ebcSMarkus Armbrusterfor my $fname (@ARGV) {
103*2dbc4ebcSMarkus Armbruster    my $text = slurp($fname);
104*2dbc4ebcSMarkus Armbruster
105*2dbc4ebcSMarkus Armbruster    $text =~ m,\A(\s*\n|\s*//\N*\n|\s*/\*.*?\*/\s*\n)*|,msg;
106*2dbc4ebcSMarkus Armbruster    my $pre = $&;
107*2dbc4ebcSMarkus Armbruster    unless ($text =~ /\G(.*\n)/g) {
108*2dbc4ebcSMarkus Armbruster        $text =~ /\G.*/;
109*2dbc4ebcSMarkus Armbruster        skipping($fname, "no recognizable header guard", "$&\n");
110*2dbc4ebcSMarkus Armbruster        next;
111*2dbc4ebcSMarkus Armbruster    }
112*2dbc4ebcSMarkus Armbruster    my $line1 = $1;
113*2dbc4ebcSMarkus Armbruster    unless ($text =~ /\G(.*\n)/g) {
114*2dbc4ebcSMarkus Armbruster        $text =~ /\G.*/;
115*2dbc4ebcSMarkus Armbruster        skipping($fname, "no recognizable header guard", "$&\n");
116*2dbc4ebcSMarkus Armbruster        next;
117*2dbc4ebcSMarkus Armbruster    }
118*2dbc4ebcSMarkus Armbruster    my $line2 = $1;
119*2dbc4ebcSMarkus Armbruster    my $body = substr($text, pos($text));
120*2dbc4ebcSMarkus Armbruster
121*2dbc4ebcSMarkus Armbruster    unless ($line1 =~ /^\s*\#\s*(if\s*\!\s*defined(\s*\()?|ifndef)\s*
122*2dbc4ebcSMarkus Armbruster                       ([A-Za-z0-9_]+)/x) {
123*2dbc4ebcSMarkus Armbruster        skipping($fname, "no recognizable header guard", $line1, $line2);
124*2dbc4ebcSMarkus Armbruster        next;
125*2dbc4ebcSMarkus Armbruster    }
126*2dbc4ebcSMarkus Armbruster    my $guard = $3;
127*2dbc4ebcSMarkus Armbruster    unless ($line2 =~ /^\s*\#\s*define\s+([A-Za-z0-9_]+)/) {
128*2dbc4ebcSMarkus Armbruster        skipping($fname, "no recognizable header guard", $line1, $line2);
129*2dbc4ebcSMarkus Armbruster        next;
130*2dbc4ebcSMarkus Armbruster    }
131*2dbc4ebcSMarkus Armbruster    my $guard2 = $1;
132*2dbc4ebcSMarkus Armbruster    unless ($guard2 eq $guard) {
133*2dbc4ebcSMarkus Armbruster        skipping($fname, "mismatched header guard ($guard vs. $guard2) ",
134*2dbc4ebcSMarkus Armbruster                 $line1, $line2);
135*2dbc4ebcSMarkus Armbruster        next;
136*2dbc4ebcSMarkus Armbruster    }
137*2dbc4ebcSMarkus Armbruster
138*2dbc4ebcSMarkus Armbruster    unless ($body =~ m,\A((.*\n)*)
139*2dbc4ebcSMarkus Armbruster                       (\s*\#\s*endif\s*(/\*\s*.*\s*\*/\s*)?\n?)
140*2dbc4ebcSMarkus Armbruster                       (\n|\s)*\Z,x) {
141*2dbc4ebcSMarkus Armbruster        skipping($fname, "can't find end of header guard");
142*2dbc4ebcSMarkus Armbruster        next;
143*2dbc4ebcSMarkus Armbruster    }
144*2dbc4ebcSMarkus Armbruster    $body = $1;
145*2dbc4ebcSMarkus Armbruster    my $line3 = $3;
146*2dbc4ebcSMarkus Armbruster    my $endif_comment = $4;
147*2dbc4ebcSMarkus Armbruster
148*2dbc4ebcSMarkus Armbruster    my $oldg = $guard;
149*2dbc4ebcSMarkus Armbruster
150*2dbc4ebcSMarkus Armbruster    unless ($fname =~ $exclude) {
151*2dbc4ebcSMarkus Armbruster        my @issues = ();
152*2dbc4ebcSMarkus Armbruster        $guard =~ tr/a-z/A-Z/
153*2dbc4ebcSMarkus Armbruster            and push @issues, "contains lowercase letters";
154*2dbc4ebcSMarkus Armbruster        $guard =~ s/^_+//
155*2dbc4ebcSMarkus Armbruster            and push @issues, "is a reserved identifier";
156*2dbc4ebcSMarkus Armbruster        $guard =~ s/(_H)?_*$/_H/
157*2dbc4ebcSMarkus Armbruster            and $& ne "_H" and push @issues, "doesn't end with _H";
158*2dbc4ebcSMarkus Armbruster        unless ($guard =~ /^[A-Z][A-Z0-9_]*_H/) {
159*2dbc4ebcSMarkus Armbruster            skipping($fname, "can't clean up odd guard symbol $oldg\n",
160*2dbc4ebcSMarkus Armbruster                     $line1, $line2);
161*2dbc4ebcSMarkus Armbruster            next;
162*2dbc4ebcSMarkus Armbruster        }
163*2dbc4ebcSMarkus Armbruster
164*2dbc4ebcSMarkus Armbruster        my $exp = fname2guard($fname =~ s,.*/,,r);
165*2dbc4ebcSMarkus Armbruster        unless ($guard =~ /\Q$exp\E\Z/) {
166*2dbc4ebcSMarkus Armbruster            $guard = fname2guard($fname =~ s,^include/,,r);
167*2dbc4ebcSMarkus Armbruster            push @issues, "doesn't match the file name";
168*2dbc4ebcSMarkus Armbruster        }
169*2dbc4ebcSMarkus Armbruster        if (@issues and $opt_v) {
170*2dbc4ebcSMarkus Armbruster            print "$fname guard $oldg needs cleanup:\n    ",
171*2dbc4ebcSMarkus Armbruster                join(", ", @issues), "\n";
172*2dbc4ebcSMarkus Armbruster        }
173*2dbc4ebcSMarkus Armbruster    }
174*2dbc4ebcSMarkus Armbruster
175*2dbc4ebcSMarkus Armbruster    $old_guard{$guard} = $oldg
176*2dbc4ebcSMarkus Armbruster        if $guard ne $oldg;
177*2dbc4ebcSMarkus Armbruster
178*2dbc4ebcSMarkus Armbruster    if (exists $guarded{$guard}) {
179*2dbc4ebcSMarkus Armbruster        gripe($fname, "guard $guard also used by $guarded{$guard}");
180*2dbc4ebcSMarkus Armbruster    } else {
181*2dbc4ebcSMarkus Armbruster        $guarded{$guard} = $fname;
182*2dbc4ebcSMarkus Armbruster    }
183*2dbc4ebcSMarkus Armbruster
184*2dbc4ebcSMarkus Armbruster    unless ($fname =~ $exclude) {
185*2dbc4ebcSMarkus Armbruster        my $newl1 = "#ifndef $guard\n";
186*2dbc4ebcSMarkus Armbruster        my $newl2 = "#define $guard\n";
187*2dbc4ebcSMarkus Armbruster        my $newl3 = "#endif\n";
188*2dbc4ebcSMarkus Armbruster        $newl3 =~ s,\Z, /* $guard */, if defined $endif_comment;
189*2dbc4ebcSMarkus Armbruster        if ($line1 ne $newl1 or $line2 ne $newl2 or $line3 ne $newl3) {
190*2dbc4ebcSMarkus Armbruster            $pre =~ s/\n*\Z/\n\n/ if $pre =~ /\N/;
191*2dbc4ebcSMarkus Armbruster            $body =~ s/\A\n*/\n/;
192*2dbc4ebcSMarkus Armbruster            if ($opt_n) {
193*2dbc4ebcSMarkus Armbruster                print "$fname would be cleaned up\n" if $opt_v;
194*2dbc4ebcSMarkus Armbruster            } else {
195*2dbc4ebcSMarkus Armbruster                unslurp($fname, "$pre$newl1$newl2$body$newl3");
196*2dbc4ebcSMarkus Armbruster                print "$fname cleaned up\n" if $opt_v;
197*2dbc4ebcSMarkus Armbruster            }
198*2dbc4ebcSMarkus Armbruster        }
199*2dbc4ebcSMarkus Armbruster    }
200*2dbc4ebcSMarkus Armbruster
201*2dbc4ebcSMarkus Armbruster    preprocess($fname, $opt_n ? $oldg : $guard)
202*2dbc4ebcSMarkus Armbruster        unless $fname =~ $exclude or $fname =~ $exclude_cpp;
203*2dbc4ebcSMarkus Armbruster}
204*2dbc4ebcSMarkus Armbruster
205*2dbc4ebcSMarkus Armbrusterif (%old_guard) {
206*2dbc4ebcSMarkus Armbruster    print STDERR "warning: guard symbol renaming may break things\n";
207*2dbc4ebcSMarkus Armbruster    for my $guard (sort keys %old_guard) {
208*2dbc4ebcSMarkus Armbruster        print STDERR "    $old_guard{$guard} -> $guard\n";
209*2dbc4ebcSMarkus Armbruster    }
210*2dbc4ebcSMarkus Armbruster    print STDERR "To find uses that may have to be updated try:\n";
211*2dbc4ebcSMarkus Armbruster    print STDERR "    git grep -Ew '", join("|", sort values %old_guard),
212*2dbc4ebcSMarkus Armbruster        "'\n";
213*2dbc4ebcSMarkus Armbruster}
214