clean-header-guards.pl (6545B)
1 #!/usr/bin/env perl 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 occurrences 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 30 use strict; 31 use warnings; 32 use Getopt::Std; 33 34 # Stuff we don't want to clean because we import it into our tree: 35 my $exclude = qr,^(include/standard-headers/|linux-headers/ 36 |pc-bios/|tests/tcg/|tests/multiboot/),x; 37 # Stuff that is expected to fail the preprocessing test: 38 my $exclude_cpp = qr,^include/libdecnumber/decNumberLocal.h,; 39 40 my %guarded = (); 41 my %old_guard = (); 42 43 our $opt_c = "cc"; 44 our $opt_n = 0; 45 our $opt_v = 0; 46 getopts("c:nv"); 47 48 sub skipping { 49 my ($fname, $msg, $line1, $line2) = @_; 50 51 return if !$opt_v or $fname =~ $exclude; 52 print "$fname skipped: $msg\n"; 53 print " $line1" if defined $line1; 54 print " $line2" if defined $line2; 55 } 56 57 sub gripe { 58 my ($fname, $msg) = @_; 59 return if $fname =~ $exclude; 60 print STDERR "$fname: warning: $msg\n"; 61 } 62 63 sub slurp { 64 my ($fname) = @_; 65 local $/; # slurp 66 open(my $in, "<", $fname) 67 or die "can't open $fname for reading: $!"; 68 return <$in>; 69 } 70 71 sub unslurp { 72 my ($fname, $contents) = @_; 73 open (my $out, ">", $fname) 74 or die "can't open $fname for writing: $!"; 75 print $out $contents 76 or die "error writing $fname: $!"; 77 close $out 78 or die "error writing $fname: $!"; 79 } 80 81 sub fname2guard { 82 my ($fname) = @_; 83 $fname =~ tr/a-z/A-Z/; 84 $fname =~ tr/A-Z0-9/_/cs; 85 return $fname; 86 } 87 88 sub preprocess { 89 my ($fname, $guard) = @_; 90 91 open(my $pipe, "-|", "$opt_c -E -D$guard -c -P - <$fname") 92 or die "can't run $opt_c: $!"; 93 while (<$pipe>) { 94 if ($_ =~ /\S/) { 95 gripe($fname, "not blank after preprocessing"); 96 last; 97 } 98 } 99 close $pipe 100 or gripe($fname, "preprocessing failed ($opt_c exit status $?)"); 101 } 102 103 for my $fname (@ARGV) { 104 my $text = slurp($fname); 105 106 $text =~ m,\A(\s*\n|\s*//\N*\n|\s*/\*.*?\*/\s*\n)*|,sg; 107 my $pre = $&; 108 unless ($text =~ /\G(.*\n)/g) { 109 $text =~ /\G.*/; 110 skipping($fname, "no recognizable header guard", "$&\n"); 111 next; 112 } 113 my $line1 = $1; 114 unless ($text =~ /\G(.*\n)/g) { 115 $text =~ /\G.*/; 116 skipping($fname, "no recognizable header guard", "$&\n"); 117 next; 118 } 119 my $line2 = $1; 120 my $body = substr($text, pos($text)); 121 122 unless ($line1 =~ /^\s*\#\s*(if\s*\!\s*defined(\s*\()?|ifndef)\s* 123 ([A-Za-z0-9_]+)/x) { 124 skipping($fname, "no recognizable header guard", $line1, $line2); 125 next; 126 } 127 my $guard = $3; 128 unless ($line2 =~ /^\s*\#\s*define\s+([A-Za-z0-9_]+)/) { 129 skipping($fname, "no recognizable header guard", $line1, $line2); 130 next; 131 } 132 my $guard2 = $1; 133 unless ($guard2 eq $guard) { 134 skipping($fname, "mismatched header guard ($guard vs. $guard2) ", 135 $line1, $line2); 136 next; 137 } 138 139 unless ($body =~ m,\A((.*\n)*) 140 ([ \t]*\#[ \t]*endif([ \t]*\N*)\n) 141 ((?s)(\s*\n|\s*//\N*\n|\s*/\*.*?\*/\s*\n)*) 142 \Z,x) { 143 skipping($fname, "can't find end of header guard"); 144 next; 145 } 146 $body = $1; 147 my $line3 = $3; 148 my $endif_comment = $4; 149 my $post = $5; 150 151 my $oldg = $guard; 152 153 unless ($fname =~ $exclude) { 154 my @issues = (); 155 $guard =~ tr/a-z/A-Z/ 156 and push @issues, "contains lowercase letters"; 157 $guard =~ s/^_+// 158 and push @issues, "is a reserved identifier"; 159 $guard =~ s/(_H)?_*$/_H/ 160 and $& ne "_H" and push @issues, "doesn't end with _H"; 161 unless ($guard =~ /^[A-Z][A-Z0-9_]*_H/) { 162 skipping($fname, "can't clean up odd guard symbol $oldg\n", 163 $line1, $line2); 164 next; 165 } 166 167 my $exp = fname2guard($fname =~ s,.*/,,r); 168 unless ($guard =~ /\Q$exp\E\Z/) { 169 $guard = fname2guard($fname =~ s,^include/,,r); 170 push @issues, "doesn't match the file name"; 171 } 172 if (@issues and $opt_v) { 173 print "$fname guard $oldg needs cleanup:\n ", 174 join(", ", @issues), "\n"; 175 } 176 } 177 178 $old_guard{$guard} = $oldg 179 if $guard ne $oldg; 180 181 if (exists $guarded{$guard}) { 182 gripe($fname, "guard $guard also used by $guarded{$guard}"); 183 } else { 184 $guarded{$guard} = $fname; 185 } 186 187 unless ($fname =~ $exclude) { 188 my $newl1 = "#ifndef $guard\n"; 189 my $newl2 = "#define $guard\n"; 190 my $newl3 = "#endif\n"; 191 $newl3 =~ s,\Z, /* $guard */, if $endif_comment; 192 if ($line1 ne $newl1 or $line2 ne $newl2 or $line3 ne $newl3) { 193 $pre =~ s/\n*\Z/\n\n/ if $pre =~ /\N/; 194 $body =~ s/\A\n*/\n/; 195 if ($opt_n) { 196 print "$fname would be cleaned up\n" if $opt_v; 197 } else { 198 unslurp($fname, "$pre$newl1$newl2$body$newl3$post"); 199 print "$fname cleaned up\n" if $opt_v; 200 } 201 } 202 } 203 204 preprocess($fname, $opt_n ? $oldg : $guard) 205 unless $fname =~ $exclude or $fname =~ $exclude_cpp; 206 } 207 208 if (%old_guard) { 209 print STDERR "warning: guard symbol renaming may break things\n"; 210 for my $guard (sort keys %old_guard) { 211 print STDERR " $old_guard{$guard} -> $guard\n"; 212 } 213 print STDERR "To find uses that may have to be updated try:\n"; 214 print STDERR " git grep -Ew '", join("|", sort values %old_guard), 215 "'\n"; 216 }