From 2d11d26113dd88072e09f92ad3f2032a27e4d8ce Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 15 Jul 2006 03:27:42 +0000 Subject: [PATCH] Create a tool to catch #include omissions that might not result in any compiler warning, specifically #ifdef or #if defined tests on symbols that are defined in a file not included. The results are a bit noisy and require care to interpret, but it's a lot better than no tool at all. --- src/tools/pginclude/README | 6 + src/tools/pginclude/pgcheckdefines | 240 +++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100755 src/tools/pginclude/pgcheckdefines diff --git a/src/tools/pginclude/README b/src/tools/pginclude/README index 0100562cfe..ac6cd7edfe 100644 --- a/src/tools/pginclude/README +++ b/src/tools/pginclude/README @@ -10,6 +10,11 @@ pgcompinclude [-v] pgrminclude [-v] remove extra #include's +pgcheckdefines + check for #ifdef tests on symbols defined in files that + weren't included --- this is a necessary sanity check on + pgrminclude! + pgdefine create macro calls for all defines in the file (used by the above routines) @@ -22,3 +27,4 @@ order would be: pgrminclude /src/include pgcompinclude pgrminclude / + pgcheckdefines diff --git a/src/tools/pginclude/pgcheckdefines b/src/tools/pginclude/pgcheckdefines new file mode 100755 index 0000000000..07ae7b5d67 --- /dev/null +++ b/src/tools/pginclude/pgcheckdefines @@ -0,0 +1,240 @@ +#! /usr/bin/perl -w + +# +# This script looks for symbols that are referenced in #ifdef or defined() +# tests without having #include'd the file that defines them. Since this +# situation won't necessarily lead to any compiler message, it seems worth +# having an automated check for it. In particular, use this to audit the +# results of pgrminclude! +# +# Usage: configure and build a PG source tree (non-VPATH), then start this +# script at the top level. It's best to enable as many configure options +# as you can, especially --enable-cassert which is known to affect include +# requirements. NB: you MUST use gcc, unless you have another compiler that +# can be persuaded to spit out the names of referenced include files. +# +# The results are necessarily platform-dependent, so use care in interpreting +# them. We try to process all .c files, even those not intended for the +# current platform, so there will be some phony failures. +# +# $PostgreSQL: pgsql/src/tools/pginclude/pgcheckdefines,v 1.1 2006/07/15 03:27:42 tgl Exp $ +# + +use Cwd; +use File::Basename; + +$topdir = cwd(); + +# Programs to use +$FIND = "find"; +$MAKE = "make"; + +# +# Build arrays of all the .c and .h files in the tree +# +# We ignore .h files under src/include/port/, since only the one exposed as +# src/include/port.h is interesting. (XXX Windows ports have additional +# files there?) Ditto for .h files in src/backend/port/ subdirectories. +# Including these .h files would clutter the list of define'd symbols and +# cause a lot of false-positive results. +# +open PIPE, "$FIND * -type f -name '*.c' |" + or die "can't fork: $!"; +while () { + chomp; + push @cfiles, $_; +} +close PIPE or die "$FIND failed: $!"; + +open PIPE, "$FIND * -type f -name '*.h' |" + or die "can't fork: $!"; +while () { + chomp; + push @hfiles, $_ unless + m|^src/include/port/| || + m|^src/backend/port/\w+/|; +} +close PIPE or die "$FIND failed: $!"; + +# +# For each .h file, extract all the symbols it #define's, and add them to +# a hash table. To cover the possibility of multiple .h files defining +# the same symbol, we make each hash entry a hash of filenames. +# +foreach $hfile (@hfiles) { + open HFILE, $hfile + or die "can't open $hfile: $!"; + while () { + if (m/^\s*#\s*define\s+(\w+)/) { + $defines{$1}{$hfile} = 1; + } + } + close HFILE; +} + +# +# For each file (both .h and .c), run the compiler to get a list of what +# files it #include's. Then extract all the symbols it tests for defined-ness, +# and check each one against the previously built hashtable. +# +foreach $file (@hfiles, @cfiles) { + ($fname, $fpath) = fileparse($file); + chdir $fpath or die "can't chdir to $fpath: $!"; + # + # Ask 'make' to parse the makefile so we can get the correct flags to + # use. CPPFLAGS in particular varies for each subdirectory. If we are + # processing a .h file, we might be in a subdirectory that has no + # Makefile, in which case we have to fake it. Note that there seems + # no easy way to prevent make from recursing into subdirectories and + # hence printing multiple definitions --- we keep the last one, which + # should come from the current Makefile. + # + if (-f "Makefile" || -f "GNUmakefile") { + $MAKECMD = "$MAKE -qp"; + } else { + $subdir = $fpath; + chop $subdir; + $top_builddir = ".."; + $tmp = $fpath; + while (($tmp = dirname($tmp)) ne '.') { + $top_builddir = $top_builddir . "/.."; + } + $MAKECMD = "$MAKE -qp 'subdir=$subdir' 'top_builddir=$top_builddir' -f '$top_builddir/src/Makefile.global'"; + } + open PIPE, "$MAKECMD |" + or die "can't fork: $!"; + while () { + if (m/^CPPFLAGS :?= (.*)/) { + $CPPFLAGS = $1; + } elsif (m/^CFLAGS :?= (.*)/) { + $CFLAGS = $1; + } elsif (m/^CFLAGS_SL :?= (.*)/) { + $CFLAGS_SL = $1; + } elsif (m/^PTHREAD_CFLAGS :?= (.*)/) { + $PTHREAD_CFLAGS = $1; + } elsif (m/^CC :?= (.*)/) { + $CC = $1; + } + } + # If make exits with status 1, it's not an error, it just means make + # thinks some files may not be up-to-date. Only complain on status 2. + close PIPE; + die "$MAKE failed in $fpath\n" if $? != 0 && $? != 256; + + # Expand out stuff that might be referenced in CFLAGS + $CFLAGS =~ s/\$\(CFLAGS_SL\)/$CFLAGS_SL/; + $CFLAGS =~ s/\$\(PTHREAD_CFLAGS\)/$PTHREAD_CFLAGS/; + + # + # Run the compiler (which had better be gcc) to get the inclusions. + # "gcc -H" reports inclusions on stderr as "... filename" where the + # number of dots varies according to nesting depth. + # + @includes = (); + $COMPILE = "$CC $CPPFLAGS $CFLAGS -H -E $fname"; + open PIPE, "$COMPILE 2>&1 >/dev/null |" + or die "can't fork: $!"; + while () { + if (m/^\.+ (.*)/) { + $include = $1; + # Ignore system headers (absolute paths); but complain if a + # .c file includes a system header before any PG header. + if ($include =~ m|^/|) { + warn "$file includes $include before any Postgres inclusion\n" + if $#includes == -1 && $file =~ m/\.c$/; + next; + } + # Strip any "./" (assume this appears only at front) + $include =~ s|^\./||; + # Make path relative to top of tree + $ipath = $fpath; + while ($include =~ s|^\.\./||) { + $ipath = dirname($ipath) . "/"; + } + $ipath =~ s|^\./||; + push @includes, $ipath . $include; + } else { + warn "$CC: $_"; + } + } + # The compiler might fail, particularly if we are checking a file that's + # not supposed to be compiled at all on the current platform, so don't + # quit on nonzero status. + close PIPE or warn "$COMPILE failed in $fpath\n"; + + # + # Scan the file to find #ifdef, #ifndef, and #if defined() constructs + # We assume #ifdef isn't continued across lines, and that defined(foo) + # isn't split across lines either + # + open FILE, $fname + or die "can't open $file: $!"; + $inif = 0; + while () { + $line = $_; + if ($line =~ m/^\s*#\s*ifdef\s+(\w+)/) { + $symbol = $1; + &checkit; + } + if ($line =~ m/^\s*#\s*ifndef\s+(\w+)/) { + $symbol = $1; + &checkit; + } + if ($line =~ m/^\s*#\s*if\s+/) { + $inif = 1; + } + if ($inif) { + while ($line =~ s/\bdefined(\s+|\s*\(\s*)(\w+)//) { + $symbol = $2; + &checkit; + } + if (!($line =~ m/\\$/)) { + $inif = 0; + } + } + } + close FILE; + + chdir $topdir or die "can't chdir to $topdir: $!"; +} + +exit 0; + +# Check an is-defined reference +sub checkit { + # Ignore if symbol isn't defined in any PG include files + if (! defined $defines{$symbol}) { + return; + } + # + # Try to match source(s) of symbol to the inclusions of the current file + # (including itself). We consider it OK if any one matches. + # + # Note: these tests aren't bulletproof; in theory the inclusion might + # occur after the use of the symbol. Given our normal file layout, + # however, the risk is minimal. + # + foreach $deffile (keys %{ $defines{$symbol} }) { + return if $deffile eq $file; + foreach $reffile (@includes) { + return if $deffile eq $reffile; + } + } + # + # If current file is a .h file, it's OK for it to assume that one of the + # base headers (postgres.h or postgres_fe.h) has been included. + # + if ($file =~ m/\.h$/) { + foreach $deffile (keys %{ $defines{$symbol} }) { + return if $deffile eq 'src/include/c.h'; + return if $deffile eq 'src/include/postgres.h'; + return if $deffile eq 'src/include/postgres_fe.h'; + return if $deffile eq 'src/include/pg_config.h'; + return if $deffile eq 'src/include/pg_config_manual.h'; + } + } + # + @places = keys %{ $defines{$symbol} }; + print "$file references $symbol, defined in @places\n"; + # print "includes: @includes\n"; +}