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