mirror of https://github.com/postgres/postgres
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.REL8_2_STABLE
parent
98bac16e4d
commit
2d11d26113
@ -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 (<PIPE>) { |
||||
chomp; |
||||
push @cfiles, $_; |
||||
} |
||||
close PIPE or die "$FIND failed: $!"; |
||||
|
||||
open PIPE, "$FIND * -type f -name '*.h' |" |
||||
or die "can't fork: $!"; |
||||
while (<PIPE>) { |
||||
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 (<HFILE>) { |
||||
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 (<PIPE>) { |
||||
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 (<PIPE>) { |
||||
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 (<FILE>) { |
||||
$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"; |
||||
} |
Loading…
Reference in new issue