Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 2d11d26

Browse files
committed
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.
1 parent 98bac16 commit 2d11d26

File tree

2 files changed

+246
-0
lines changed

2 files changed

+246
-0
lines changed

src/tools/pginclude/README

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ pgcompinclude [-v]
1010
pgrminclude [-v]
1111
remove extra #include's
1212

13+
pgcheckdefines
14+
check for #ifdef tests on symbols defined in files that
15+
weren't included --- this is a necessary sanity check on
16+
pgrminclude!
17+
1318
pgdefine create macro calls for all defines in the file (used by
1419
the above routines)
1520

@@ -22,3 +27,4 @@ order would be:
2227
pgrminclude /src/include
2328
pgcompinclude
2429
pgrminclude /
30+
pgcheckdefines

src/tools/pginclude/pgcheckdefines

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
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+
or die "can't fork: $!";
43+
while (<PIPE>) {
44+
chomp;
45+
push @cfiles, $_;
46+
}
47+
close PIPE or die "$FIND failed: $!";
48+
49+
open PIPE, "$FIND * -type f -name '*.h' |"
50+
or die "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 PIPE or die "$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+
or die "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 $fpath or die "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+
or die "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+
or die "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 PIPE or warn "$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+
or die "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 $topdir or die "can't chdir to $topdir: $!";
199+
}
200+
201+
exit 0;
202+
203+
# Check an is-defined reference
204+
sub checkit {
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+
return if $deffile eq $file;
219+
foreach $reffile (@includes) {
220+
return if $deffile eq $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+
return if $deffile eq 'src/include/c.h';
230+
return if $deffile eq 'src/include/postgres.h';
231+
return if $deffile eq 'src/include/postgres_fe.h';
232+
return if $deffile eq 'src/include/pg_config.h';
233+
return if $deffile eq '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+
}

0 commit comments

Comments
 (0)