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