blob: 2936caf2e075fe850ff649b30725db6552bd089d [file] [log] [blame]
#! /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.
#
# src/tools/pginclude/pgcheckdefines
#
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";
}