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