summaryrefslogtreecommitdiff
path: root/src/tools/pginclude/pgcheckdefines
blob: 2936caf2e075fe850ff649b30725db6552bd089d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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.
#
# 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";
}