|
| 1 | +#! /usr/bin/perl -w |
| 2 | + |
| 3 | +# |
| 4 | +# This script looks for symbols that are referenced in #ifdef or defined() |
| 5 | +# tests without having #include'd the file that defines them. Since this |
| 6 | +# situation won't necessarily lead to any compiler message, it seems worth |
| 7 | +# having an automated check for it. In particular, use this to audit the |
| 8 | +# results of pgrminclude! |
| 9 | +# |
| 10 | +# Usage: configure and build a PG source tree (non-VPATH), then start this |
| 11 | +# script at the top level. It's best to enable as many configure options |
| 12 | +# as you can, especially --enable-cassert which is known to affect include |
| 13 | +# requirements. NB: you MUST use gcc, unless you have another compiler that |
| 14 | +# can be persuaded to spit out the names of referenced include files. |
| 15 | +# |
| 16 | +# The results are necessarily platform-dependent, so use care in interpreting |
| 17 | +# them. We try to process all .c files, even those not intended for the |
| 18 | +# current platform, so there will be some phony failures. |
| 19 | +# |
| 20 | +# $PostgreSQL: pgsql/src/tools/pginclude/pgcheckdefines,v 1.1 2006/07/15 03:27:42 tgl Exp $ |
| 21 | +# |
| 22 | + |
| 23 | +use Cwd; |
| 24 | +use File::Basename; |
| 25 | + |
| 26 | +$topdir = cwd(); |
| 27 | + |
| 28 | +# Programs to use |
| 29 | +$FIND ="find"; |
| 30 | +$MAKE ="make"; |
| 31 | + |
| 32 | +# |
| 33 | +# Build arrays of all the .c and .h files in the tree |
| 34 | +# |
| 35 | +# We ignore .h files under src/include/port/, since only the one exposed as |
| 36 | +# src/include/port.h is interesting. (XXX Windows ports have additional |
| 37 | +# files there?) Ditto for .h files in src/backend/port/ subdirectories. |
| 38 | +# Including these .h files would clutter the list of define'd symbols and |
| 39 | +# cause a lot of false-positive results. |
| 40 | +# |
| 41 | +open PIPE,"$FIND * -type f -name '*.c' |" |
| 42 | +ordie"can't fork:$!"; |
| 43 | +while (<PIPE>) { |
| 44 | +chomp; |
| 45 | +push@cfiles,$_; |
| 46 | +} |
| 47 | +close PIPEordie"$FIND failed:$!"; |
| 48 | + |
| 49 | +open PIPE,"$FIND * -type f -name '*.h' |" |
| 50 | +ordie"can't fork:$!"; |
| 51 | +while (<PIPE>) { |
| 52 | +chomp; |
| 53 | +push@hfiles,$_unless |
| 54 | +m|^src/include/port/| || |
| 55 | +m|^src/backend/port/\w+/|; |
| 56 | +} |
| 57 | +close PIPEordie"$FIND failed:$!"; |
| 58 | + |
| 59 | +# |
| 60 | +# For each .h file, extract all the symbols it #define's, and add them to |
| 61 | +# a hash table. To cover the possibility of multiple .h files defining |
| 62 | +# the same symbol, we make each hash entry a hash of filenames. |
| 63 | +# |
| 64 | +foreach$hfile (@hfiles) { |
| 65 | +open HFILE,$hfile |
| 66 | +ordie"can't open$hfile:$!"; |
| 67 | +while (<HFILE>) { |
| 68 | +if (m/^\s*#\s*define\s+(\w+)/) { |
| 69 | +$defines{$1}{$hfile} = 1; |
| 70 | +} |
| 71 | + } |
| 72 | +close HFILE; |
| 73 | +} |
| 74 | + |
| 75 | +# |
| 76 | +# For each file (both .h and .c), run the compiler to get a list of what |
| 77 | +# files it #include's. Then extract all the symbols it tests for defined-ness, |
| 78 | +# and check each one against the previously built hashtable. |
| 79 | +# |
| 80 | +foreach$file (@hfiles,@cfiles) { |
| 81 | + ($fname,$fpath) = fileparse($file); |
| 82 | +chdir$fpathordie"can't chdir to$fpath:$!"; |
| 83 | +# |
| 84 | +# Ask 'make' to parse the makefile so we can get the correct flags to |
| 85 | +# use. CPPFLAGS in particular varies for each subdirectory. If we are |
| 86 | +# processing a .h file, we might be in a subdirectory that has no |
| 87 | +# Makefile, in which case we have to fake it. Note that there seems |
| 88 | +# no easy way to prevent make from recursing into subdirectories and |
| 89 | +# hence printing multiple definitions --- we keep the last one, which |
| 90 | +# should come from the current Makefile. |
| 91 | +# |
| 92 | +if (-f"Makefile" ||-f"GNUmakefile") { |
| 93 | +$MAKECMD ="$MAKE -qp"; |
| 94 | + }else { |
| 95 | +$subdir =$fpath; |
| 96 | +chop$subdir; |
| 97 | +$top_builddir =".."; |
| 98 | +$tmp =$fpath; |
| 99 | +while (($tmp = dirname($tmp))ne'.') { |
| 100 | +$top_builddir =$top_builddir ."/.."; |
| 101 | +} |
| 102 | +$MAKECMD ="$MAKE -qp 'subdir=$subdir' 'top_builddir=$top_builddir' -f '$top_builddir/src/Makefile.global'"; |
| 103 | + } |
| 104 | +open PIPE,"$MAKECMD |" |
| 105 | +ordie"can't fork:$!"; |
| 106 | +while (<PIPE>) { |
| 107 | +if (m/^CPPFLAGS :?= (.*)/) { |
| 108 | +$CPPFLAGS =$1; |
| 109 | +}elsif (m/^CFLAGS :?= (.*)/) { |
| 110 | +$CFLAGS =$1; |
| 111 | +}elsif (m/^CFLAGS_SL :?= (.*)/) { |
| 112 | +$CFLAGS_SL =$1; |
| 113 | +}elsif (m/^PTHREAD_CFLAGS :?= (.*)/) { |
| 114 | +$PTHREAD_CFLAGS =$1; |
| 115 | +}elsif (m/^CC :?= (.*)/) { |
| 116 | +$CC =$1; |
| 117 | +} |
| 118 | + } |
| 119 | +# If make exits with status 1, it's not an error, it just means make |
| 120 | +# thinks some files may not be up-to-date. Only complain on status 2. |
| 121 | +close PIPE; |
| 122 | +die"$MAKE failed in$fpath\n"if$? != 0 &&$? != 256; |
| 123 | + |
| 124 | +# Expand out stuff that might be referenced in CFLAGS |
| 125 | +$CFLAGS =~s/\$\(CFLAGS_SL\)/$CFLAGS_SL/; |
| 126 | +$CFLAGS =~s/\$\(PTHREAD_CFLAGS\)/$PTHREAD_CFLAGS/; |
| 127 | + |
| 128 | +# |
| 129 | +# Run the compiler (which had better be gcc) to get the inclusions. |
| 130 | +# "gcc -H" reports inclusions on stderr as "... filename" where the |
| 131 | +# number of dots varies according to nesting depth. |
| 132 | +# |
| 133 | +@includes = (); |
| 134 | +$COMPILE ="$CC$CPPFLAGS$CFLAGS -H -E$fname"; |
| 135 | +open PIPE,"$COMPILE 2>&1 >/dev/null |" |
| 136 | +ordie"can't fork:$!"; |
| 137 | +while (<PIPE>) { |
| 138 | +if (m/^\.+ (.*)/) { |
| 139 | +$include =$1; |
| 140 | +# Ignore system headers (absolute paths); but complain if a |
| 141 | +# .c file includes a system header before any PG header. |
| 142 | +if ($include =~m|^/|) { |
| 143 | +warn"$file includes$include before any Postgres inclusion\n" |
| 144 | +if$#includes == -1 &&$file =~m/\.c$/; |
| 145 | +next; |
| 146 | + } |
| 147 | +# Strip any "./" (assume this appears only at front) |
| 148 | +$include =~s|^\./||; |
| 149 | +# Make path relative to top of tree |
| 150 | +$ipath =$fpath; |
| 151 | +while ($include =~s|^\.\./||) { |
| 152 | +$ipath = dirname($ipath) ."/"; |
| 153 | + } |
| 154 | +$ipath =~s|^\./||; |
| 155 | +push@includes,$ipath .$include; |
| 156 | +}else { |
| 157 | +warn"$CC:$_"; |
| 158 | +} |
| 159 | + } |
| 160 | +# The compiler might fail, particularly if we are checking a file that's |
| 161 | +# not supposed to be compiled at all on the current platform, so don't |
| 162 | +# quit on nonzero status. |
| 163 | +close PIPEorwarn"$COMPILE failed in$fpath\n"; |
| 164 | + |
| 165 | +# |
| 166 | +# Scan the file to find #ifdef, #ifndef, and #if defined() constructs |
| 167 | +# We assume #ifdef isn't continued across lines, and that defined(foo) |
| 168 | +# isn't split across lines either |
| 169 | +# |
| 170 | +open FILE,$fname |
| 171 | +ordie"can't open$file:$!"; |
| 172 | +$inif = 0; |
| 173 | +while (<FILE>) { |
| 174 | +$line =$_; |
| 175 | +if ($line =~m/^\s*#\s*ifdef\s+(\w+)/) { |
| 176 | +$symbol =$1; |
| 177 | + &checkit; |
| 178 | +} |
| 179 | +if ($line =~m/^\s*#\s*ifndef\s+(\w+)/) { |
| 180 | +$symbol =$1; |
| 181 | + &checkit; |
| 182 | +} |
| 183 | +if ($line =~m/^\s*#\s*if\s+/) { |
| 184 | +$inif = 1; |
| 185 | +} |
| 186 | +if ($inif) { |
| 187 | +while ($line =~s/\bdefined(\s+|\s*\(\s*)(\w+)//) { |
| 188 | +$symbol =$2; |
| 189 | +&checkit; |
| 190 | + } |
| 191 | +if (!($line =~m/\\$/)) { |
| 192 | +$inif = 0; |
| 193 | + } |
| 194 | +} |
| 195 | + } |
| 196 | +close FILE; |
| 197 | + |
| 198 | +chdir$topdirordie"can't chdir to$topdir:$!"; |
| 199 | +} |
| 200 | + |
| 201 | +exit 0; |
| 202 | + |
| 203 | +# Check an is-defined reference |
| 204 | +subcheckit { |
| 205 | +# Ignore if symbol isn't defined in any PG include files |
| 206 | +if (!defined$defines{$symbol}) { |
| 207 | +return; |
| 208 | + } |
| 209 | +# |
| 210 | +# Try to match source(s) of symbol to the inclusions of the current file |
| 211 | +# (including itself). We consider it OK if any one matches. |
| 212 | +# |
| 213 | +# Note: these tests aren't bulletproof; in theory the inclusion might |
| 214 | +# occur after the use of the symbol. Given our normal file layout, |
| 215 | +# however, the risk is minimal. |
| 216 | +# |
| 217 | +foreach$deffile (keys %{$defines{$symbol} }) { |
| 218 | +returnif$deffileeq$file; |
| 219 | +foreach$reffile (@includes) { |
| 220 | +returnif$deffileeq$reffile; |
| 221 | +} |
| 222 | + } |
| 223 | +# |
| 224 | +# If current file is a .h file, it's OK for it to assume that one of the |
| 225 | +# base headers (postgres.h or postgres_fe.h) has been included. |
| 226 | +# |
| 227 | +if ($file =~m/\.h$/) { |
| 228 | +foreach$deffile (keys %{$defines{$symbol} }) { |
| 229 | +returnif$deffileeq'src/include/c.h'; |
| 230 | +returnif$deffileeq'src/include/postgres.h'; |
| 231 | +returnif$deffileeq'src/include/postgres_fe.h'; |
| 232 | +returnif$deffileeq'src/include/pg_config.h'; |
| 233 | +returnif$deffileeq'src/include/pg_config_manual.h'; |
| 234 | +} |
| 235 | + } |
| 236 | +# |
| 237 | +@places =keys %{$defines{$symbol} }; |
| 238 | +print"$file references$symbol, defined in@places\n"; |
| 239 | +# print "includes: @includes\n"; |
| 240 | +} |