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# (6) coregrind/ *.[ch] must not use tl_assert
14# (7) include/*.h and tool *.[ch] must not use vg_assert
15# (8) coregrind/ *.[ch] must not use VG_(tool_panic)
16# (9) include/*.h and tool *.[ch] must not use VG_(core_panic)
17# (10) *.S must unconditionally instantiate MARK_STACK_NO_EXEC
18#
19# There can be false positives as we don't really parse the source files.
20# Instead we only match regular expressions.
21#-------------------------------------------------------------------
22
23use strict;
24use warnings;
25use File::Basename;
26use Getopt::Long;
27
28my $this_script = basename($0);
29
30# The list of top-level directories is divided into three sets:
31#
32# (1) coregrind directories
33# (2) tool directories
34# (3) directories to ignore
35#
36# If a directory is found that does not belong to any of those sets, the
37# script will terminate unsuccessfully.
38
39my %coregrind_dirs = (
40    "include" => 1,
41    "coregrind" => 1,
42    );
43
44my %tool_dirs = (
45    "none" => 1,
46    "lackey" => 1,
47    "massif" => 1,
48    "memcheck" => 1,
49    "drd" => 1,
50    "helgrind", => 1,
51    "callgrind" => 1,
52    "cachegrind" => 1,
53    "shared" => 1,
54    "exp-bbv" => 1,
55    "exp-dhat" => 1,
56    "exp-sgcheck" => 1
57    );
58
59my %dirs_to_ignore = (
60    ".deps" => 1,
61    ".svn" => 1,
62    ".git" => 1,            # allow git mirrors of the svn repo
63    ".in_place" => 1,
64    "Inst" => 1,            # the nightly scripts creates this
65    "VEX" => 1,
66    "docs" => 1,
67    "auxprogs" => 1,
68    "autom4te.cache" => 1,
69    "nightly" => 1,
70    "perf" => 1,
71    "tests" => 1,
72    "gdbserver_tests" => 1,
73    "mpi" => 1,
74    "solaris" => 1
75    );
76
77my %tool_export_header = (
78    "drd/drd.h" => 1,
79    "helgrind/helgrind.h" => 1,
80    "memcheck/memcheck.h" => 1,
81    "callgrind/callgrind.h" => 1
82    );
83
84my $usage=<<EOF;
85USAGE
86
87  $this_script
88
89    [--debug]          Debugging output
90
91    dir ...            Directories to process
92EOF
93
94my $debug = 0;
95my $num_errors = 0;
96
97&main;
98
99sub main {
100    GetOptions( "debug"  => \$debug ) || die $usage;
101
102    my $argc = $#ARGV + 1;
103
104    if ($argc < 1) {
105        die $usage;
106    }
107
108    foreach my $dir (@ARGV) {
109        process_dir(undef, $dir, 0);
110    }
111
112    my $rc = ($num_errors == 0) ? 0 : 1;
113    exit $rc;
114}
115
116sub process_dir {
117    my ($path, $dir, $depth) = @_;
118    my $hdir;
119
120    if ($depth == 0) {
121# The root directory is always processed
122    } elsif ($depth == 1) {
123# Toplevel directories
124        return if ($dirs_to_ignore{$dir});
125
126        if (! $tool_dirs{$dir} && ! $coregrind_dirs{$dir}) {
127            die "Unknown directory '$dir'. Please update $this_script\n";
128        }
129    } else {
130# Subdirectories
131        return if ($dirs_to_ignore{$dir});
132    }
133
134    print "DIR = $dir   DEPTH = $depth\n" if ($debug);
135
136    chdir($dir) || die "Cannot chdir '$dir'\n";
137
138    opendir($hdir, ".") || die "cannot open directory '.'";
139
140    while (my $file = readdir($hdir)) {
141        next if ($file eq ".");
142        next if ($file eq "..");
143
144# Subdirectories
145        if (-d $file) {
146            my $full_path = defined $path ? "$path/$file" : $file;
147            process_dir($full_path, $file, $depth + 1);
148            next;
149        }
150
151# Regular files; only interested in *.c, *.S and *.h
152        next if (! ($file =~ /\.[cSh]$/));
153        my $path_name = defined $path ? "$path/$file" : $file;
154        process_file($path_name);
155    }
156    close($hdir);
157    chdir("..") || die "Cannot chdir '..'\n";
158}
159
160#---------------------------------------------------------------------
161# Return 1, if file is located in <valgrind>/include
162#---------------------------------------------------------------------
163sub is_coregrind_export_header {
164    my ($path_name) = @_;
165
166    return ($path_name =~ /^include\//) ? 1 : 0;
167}
168
169#---------------------------------------------------------------------
170# Return 1, if file is located underneath <valgrind>/coregrind
171#---------------------------------------------------------------------
172sub is_coregrind_file {
173    my ($path_name) = @_;
174
175    return ($path_name =~ /^coregrind\//) ? 1 : 0;
176}
177
178#---------------------------------------------------------------------
179# Return 1, if file is located underneath <valgrind>/<tool>
180#---------------------------------------------------------------------
181sub is_tool_file {
182    my ($path_name) = @_;
183
184    for my $tool (keys %tool_dirs) {
185        return 1 if ($path_name =~ /^$tool\//);
186    }
187    return 0
188}
189
190#---------------------------------------------------------------------
191# Return array of files #include'd by file.
192#---------------------------------------------------------------------
193sub get_included_files {
194    my ($path_name) = @_;
195    my @includes = ();
196    my $file = basename($path_name);
197
198    open(FILE, "<$file") || die "Cannot open file '$file'";
199
200    while (my $line = <FILE>) {
201        if ($line =~ /^\s*#\s*include "([^"]*)"/) {
202            push @includes, $1;
203        }
204        if ($line =~ /^\s*#\s*include <([^>]*)>/) {
205            push @includes, $1;
206        }
207    }
208    close FILE;
209    return @includes;
210}
211
212#---------------------------------------------------------------------
213# Check a file from <valgrind>/include
214#---------------------------------------------------------------------
215sub check_coregrind_export_header {
216    my ($path_name) = @_;
217    my $file = basename($path_name);
218
219    foreach my $inc (get_included_files($path_name)) {
220        $inc = basename($inc);
221# Must not include pub_core_....
222        if ($inc =~ /pub_core_/) {
223            error("File $path_name must not include $inc\n");
224        }
225# Only pub_tool_clreq.h may include valgrind.h
226        if (($inc eq "valgrind.h") && ($path_name ne "include/pub_tool_clreq.h")) {
227            error("File $path_name should include pub_tool_clreq.h instead of $inc\n");
228        }
229    }
230# Must not use vg_assert
231    my $assert = `grep vg_assert $file`;
232    if ($assert ne "") {
233        error("File $path_name must not use vg_assert\n");
234    }
235# Must not use VG_(core_panic)
236    my $panic = `grep 'VG_(core_panic)' $file`;
237    if ($panic ne "") {
238        error("File $path_name must not use VG_(core_panic)\n");
239    }
240}
241
242#---------------------------------------------------------------------
243# Check a file from <valgrind>/coregrind
244#---------------------------------------------------------------------
245sub check_coregrind_file {
246    my ($path_name) = @_;
247    my $file = basename($path_name);
248
249    foreach my $inc (get_included_files($path_name)) {
250        print "\tINCLUDE $inc\n" if ($debug);
251# Only pub_tool_xyzzy.h may include pub_core_xyzzy.h
252        if ($inc =~ /pub_tool_/) {
253            my $buddy = $inc;
254            $buddy =~ s/pub_tool/pub_core/;
255            if ($file ne $buddy) {
256                error("File $path_name must not include $inc\n");
257            }
258        }
259# Must not include valgrind.h
260        if ($inc eq "valgrind.h") {
261            error("File $path_name should include pub_core_clreq.h instead of $inc\n");
262        }
263    }
264# Must not use tl_assert
265    my $assert = `grep tl_assert $file`;
266    if ($assert ne "") {
267        error("File $path_name must not use tl_assert\n");
268    }
269# Must not use VG_(tool_panic)
270    my $panic = `grep 'VG_(tool_panic)' $file`;
271    if ($panic ne "") {
272        chomp($panic);
273# Do not complain about the definition of VG_(tool_panic)
274        if (($path_name eq "coregrind/m_libcassert.c") &&
275            ($panic eq "void VG_(tool_panic) ( const HChar* str )")) {
276# OK
277        } else {
278            error("File $path_name must not use VG_(tool_panic)\n");
279        }
280    }
281}
282
283#---------------------------------------------------------------------
284# Check a file from <valgrind>/<tool>
285#---------------------------------------------------------------------
286sub check_tool_file {
287    my ($path_name) = @_;
288    my $file = basename($path_name);
289
290    foreach my $inc (get_included_files($path_name)) {
291        print "\tINCLUDE $inc\n" if ($debug);
292# Must not include pub_core_...
293        if ($inc =~ /pub_core_/) {
294            error("File $path_name must not include $inc\n");
295        }
296# Must not include valgrind.h unless this is an export header
297        if ($inc eq "valgrind.h" && ! $tool_export_header{$path_name}) {
298            error("File $path_name should include pub_tool_clreq.h instead of $inc\n");
299        }
300    }
301# Must not use vg_assert
302    my $assert = `grep vg_assert $file`;
303    if ($assert ne "") {
304        error("File $path_name must not use vg_assert\n");
305    }
306# Must not use VG_(core_panic)
307    my $panic = `grep 'VG_(core_panic)' $file`;
308    if ($panic ne "") {
309        error("File $path_name must not use VG_(core_panic)\n");
310    }
311}
312
313#---------------------------------------------------------------------
314# Check an assembler file
315#---------------------------------------------------------------------
316sub check_assembler_file {
317    my ($path_name) = @_;
318    my $file = basename($path_name);
319    my $found = 0;
320
321    open(FILE, "<$file") || die "Cannot open file '$file'";
322
323    while (my $line = <FILE>) {
324        if ($line =~ /^\s*MARK_STACK_NO_EXEC/) {
325            $found = 1;
326            last;
327        }
328    }
329    if ($found == 0) {
330        error("File $path_name does not instantiate MARK_STACK_NO_EXEC\n");
331    } else {
332        while (my $line = <FILE>) {
333            if ($line =~ /^\s*#\s*endif/) {
334                error("File $path_name instantiates MARK_STACK_NO_EXEC"
335                      . " under a condition\n");
336                last;
337            }
338        }
339    }
340    close FILE;
341}
342
343sub process_file {
344    my ($path_name) = @_;
345
346    print "FILE = $path_name\n" if ($debug);
347
348    if (is_coregrind_export_header($path_name)) {
349        check_coregrind_export_header($path_name);
350    } elsif (is_coregrind_file($path_name)) {
351        check_coregrind_file($path_name);
352    } elsif (is_tool_file($path_name)) {
353        check_tool_file($path_name);
354    }
355
356    if ($path_name =~ /\.S$/) {
357        check_assembler_file($path_name);
358    }
359}
360
361sub error {
362    my ($message) = @_;
363    print STDERR "*** $message";
364    ++$num_errors;
365}
366