1#!/usr/bin/env perl
2
3#-------------------------------------------------------------------
4# Check header files and #include directives
5#
6# (1) include/*.h must not include pub_core_...h
7# (2) coregrind/pub_core_xyzzy.h may include pub_tool_xyzzy.h
8#     other coregrind headers may not include pub_tool_xyzzy.h
9# (3) coregrind/ *.c must not include pub_tool_xyzzy.h
10# (4) tool *.[ch] files must not include pub_core_...h
11# (5) include pub_core/tool_clreq.h instead of valgrind.h except in tools'
12#     export headers
13#-------------------------------------------------------------------
14
15use strict;
16use warnings;
17use File::Basename;
18use Getopt::Long;
19
20my $this_script = basename($0);
21
22# The list of top-level directories is divided into three sets:
23#
24# (1) coregrind directories
25# (2) tool directories
26# (3) directories to ignore
27#
28# If a directory is found that does not belong to any of those sets, the
29# script will terminate unsuccessfully.
30
31my %coregrind_dirs = (
32    "include" => 1,
33    "coregrind" => 1,
34    );
35
36my %tool_dirs = (
37    "none" => 1,
38    "lackey" => 1,
39    "massif" => 1,
40    "memcheck" => 1,
41    "drd" => 1,
42    "helgrind", => 1,
43    "callgrind" => 1,
44    "cachegrind" => 1,
45    "shared" => 1,
46    "exp-bbv" => 1,
47    "exp-dhat" => 1,
48    "exp-sgcheck" => 1
49    );
50
51my %dirs_to_ignore = (
52    ".deps" => 1,
53    ".svn" => 1,
54    ".git" => 1,            # allow git mirrors of the svn repo
55    ".in_place" => 1,
56    "Inst" => 1,            # the nightly scripts creates this
57    "VEX" => 1,
58    "docs" => 1,
59    "auxprogs" => 1,
60    "autom4te.cache" => 1,
61    "nightly" => 1,
62    "perf" => 1,
63    "tests" => 1,
64    "gdbserver_tests" => 1,
65    "mpi" => 1
66    );
67
68my %tool_export_header = (
69    "drd/drd.h" => 1,
70    "helgrind/helgrind.h" => 1,
71    "memcheck/memcheck.h" => 1,
72    "callgrind/callgrind.h" => 1
73    );
74
75my $usage=<<EOF;
76USAGE
77
78  $this_script
79
80    [--debug]          Debugging output
81
82    dir ...            Directories to process
83EOF
84
85my $debug = 0;
86my $num_errors = 0;
87
88&main;
89
90sub main {
91    GetOptions( "debug"  => \$debug ) || die $usage;
92
93    my $argc = $#ARGV + 1;
94
95    if ($argc < 1) {
96        die $usage;
97    }
98
99    foreach my $dir (@ARGV) {
100        process_dir(undef, $dir, 0);
101    }
102
103    my $rc = ($num_errors == 0) ? 0 : 1;
104    exit $rc;
105}
106
107sub process_dir {
108    my ($path, $dir, $depth) = @_;
109    my $hdir;
110
111    if ($depth == 0) {
112# The root directory is always processed
113    } elsif ($depth == 1) {
114# Toplevel directories
115        return if ($dirs_to_ignore{$dir});
116
117        if (! $tool_dirs{$dir} && ! $coregrind_dirs{$dir}) {
118            die "Unknown directory '$dir'. Please update $this_script\n";
119        }
120    } else {
121# Subdirectories
122        return if ($dirs_to_ignore{$dir});
123    }
124
125    print "DIR = $dir   DEPTH = $depth\n" if ($debug);
126
127    chdir($dir) || die "Cannot chdir '$dir'\n";
128
129    opendir($hdir, ".") || die "cannot open directory '.'";
130
131    while (my $file = readdir($hdir)) {
132        next if ($file eq ".");
133        next if ($file eq "..");
134
135# Subdirectories
136        if (-d $file) {
137            my $full_path = defined $path ? "$path/$file" : $file;
138            process_dir($full_path, $file, $depth + 1);
139            next;
140        }
141
142# Regular files; only interested in *.c and *.h
143        next if (! ($file =~ /\.[ch]$/));
144        my $path_name = defined $path ? "$path/$file" : $file;
145        process_file($path_name);
146    }
147    close($hdir);
148    chdir("..") || die "Cannot chdir '..'\n";
149}
150
151#---------------------------------------------------------------------
152# Return 1, if file is located in <valgrind>/include
153#---------------------------------------------------------------------
154sub is_coregrind_export_header {
155    my ($path_name) = @_;
156
157    return ($path_name =~ /^include\//) ? 1 : 0;
158}
159
160#---------------------------------------------------------------------
161# Return 1, if file is located underneath <valgrind>/coregrind
162#---------------------------------------------------------------------
163sub is_coregrind_file {
164    my ($path_name) = @_;
165
166    return ($path_name =~ /^coregrind\//) ? 1 : 0;
167}
168
169#---------------------------------------------------------------------
170# Return 1, if file is located underneath <valgrind>/<tool>
171#---------------------------------------------------------------------
172sub is_tool_file {
173    my ($path_name) = @_;
174
175    for my $tool (keys %tool_dirs) {
176        return 1 if ($path_name =~ /^$tool\//);
177    }
178    return 0
179}
180
181#---------------------------------------------------------------------
182# Return array of files #include'd by file.
183#---------------------------------------------------------------------
184sub get_included_files {
185    my ($path_name) = @_;
186    my @includes = ();
187    my $file = basename($path_name);
188
189    open(FILE, "<$file") || die "Cannot open file '$file'";
190
191    while (my $line = <FILE>) {
192        if ($line =~ /^\s*#\s*include "([^"]*)"/) {
193            push @includes, $1;
194        }
195        if ($line =~ /^\s*#\s*include <([^>]*)>/) {
196            push @includes, $1;
197        }
198    }
199    close FILE;
200    return @includes;
201}
202
203#---------------------------------------------------------------------
204# Check a file from <valgrind>/include
205#---------------------------------------------------------------------
206sub check_coregrind_export_header {
207    my ($path_name) = @_;
208
209    foreach my $inc (get_included_files($path_name)) {
210        $inc = basename($inc);
211# Must not include pub_core_....
212        if ($inc =~ /pub_core_/) {
213            error("File $path_name must not include $inc\n");
214        }
215# Only pub_tool_clreq.h may include valgrind.h
216        if (($inc eq "valgrind.h") && ($path_name ne "include/pub_tool_clreq.h")) {
217            error("File $path_name should include pub_tool_clreq.h instead of $inc\n");
218        }
219    }
220}
221
222#---------------------------------------------------------------------
223# Check a file from <valgrind>/coregrind
224#---------------------------------------------------------------------
225sub check_coregrind_file {
226    my ($path_name) = @_;
227    my $file = basename($path_name);
228
229    foreach my $inc (get_included_files($path_name)) {
230        print "\tINCLUDE $inc\n" if ($debug);
231# Only pub_tool_xyzzy.h may include pub_core_xyzzy.h
232        if ($inc =~ /pub_tool_/) {
233            my $buddy = $inc;
234            $buddy =~ s/pub_tool/pub_core/;
235            if ($file ne $buddy) {
236                error("File $path_name must not include $inc\n");
237            }
238        }
239# Must not include valgrind.h
240        if ($inc eq "valgrind.h") {
241            error("File $path_name should include pub_core_clreq.h instead of $inc\n");
242        }
243    }
244}
245
246#---------------------------------------------------------------------
247# Check a file from <valgrind>/<tool>
248#---------------------------------------------------------------------
249sub check_tool_file {
250    my ($path_name) = @_;
251    my $file = basename($path_name);
252
253    foreach my $inc (get_included_files($path_name)) {
254        print "\tINCLUDE $inc\n" if ($debug);
255# Must not include pub_core_...
256        if ($inc =~ /pub_core_/) {
257            error("File $path_name must not include $inc\n");
258        }
259# Must not include valgrind.h unless this is an export header
260        if ($inc eq "valgrind.h" && ! $tool_export_header{$path_name}) {
261            error("File $path_name should include pub_tool_clreq.h instead of $inc\n");
262        }
263    }
264}
265
266sub process_file {
267    my ($path_name) = @_;
268
269    print "FILE = $path_name\n" if ($debug);
270
271    if (is_coregrind_export_header($path_name)) {
272        check_coregrind_export_header($path_name);
273    } elsif (is_coregrind_file($path_name)) {
274        check_coregrind_file($path_name);
275    } elsif (is_tool_file($path_name)) {
276        check_tool_file($path_name);
277    }
278}
279
280sub error {
281    my ($message) = @_;
282    print STDERR "*** $message";
283    ++$num_errors;
284}
285