Skip to content

Commit 42a4d2b

Browse files
authored
Add script to check for missing #includes and automatically fix them; run them on the latest sources (#1308)
1 parent ebe88a8 commit 42a4d2b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+498
-0
lines changed

Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ EXTRA_DIST = \
2323
autogen.sh \
2424
CONTRIBUTING.md \
2525
scripts/clang-format-test.sh \
26+
scripts/test-includes.pl \
2627
INSTALL.md \
2728
ISSUES.md \
2829
LICENSE.md \

scripts/test-includes.pl

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
#!/usr/bin/perl
2+
3+
use strict;
4+
use warnings qw(FATAL all);
5+
use Cwd qw(realpath);
6+
use File::Basename;
7+
use File::Find;
8+
use File::Spec::Functions qw(abs2rel);
9+
use File::Temp;
10+
use Getopt::Long;
11+
use List::Util qw(max);
12+
use Memoize;
13+
use Pod::Usage;
14+
15+
my ($root, $fix, $quiet);
16+
17+
get_options();
18+
19+
# Confirm the presence of g++ compiler
20+
die <<EOF if `g++ --version`, $?;
21+
22+
GCC needs to be installed and g++ needs to be in the PATH.
23+
24+
This tool uses GCC-specific warnings which are not available on Clang.
25+
EOF
26+
27+
memoize(qw(check_ignore NORMALIZER realpath));
28+
find_and_check_files();
29+
print_summary();
30+
31+
# Whether a file is ignored by Git
32+
sub check_ignore {
33+
exit 2 if -257 & system qw(git check-ignore -q --), $_[0];
34+
return $? == 0;
35+
}
36+
37+
# Scan C/C++ files starting from $root
38+
# Scan .h files first, in case --fix is enabled and fixing the .h
39+
# file will automatically fix the corresponding other files
40+
sub find_and_check_files {
41+
for my $suffix (qw(.h .hpp .c .cc .cpp)) {
42+
my $filter = sub {
43+
if (-d) {
44+
# prune .git, external, and directories which are ignored
45+
$File::Find::prune = 1 if /^\.git$/ || /^external$/ || check_ignore($_);
46+
} elsif (-f) {
47+
# check files with extension which are not ignored
48+
&check_file if /\Q$suffix\E$/ && !check_ignore($_);
49+
}
50+
};
51+
find($filter, $root);
52+
}
53+
}
54+
55+
my $errors = 0;
56+
sub print_summary {
57+
print STDERR "\n" if !$quiet;
58+
if ($errors) {
59+
if ($fix) {
60+
print "\nMissing #includes found and fixed in $errors files.\n";
61+
} else {
62+
print "\nMissing #includes found in $errors files.\n";
63+
exit 1;
64+
}
65+
} else {
66+
print "\nNo missing #includes found\n\nALL TESTS PASSED\n";
67+
}
68+
exit 0;
69+
}
70+
71+
# Get the command-line options
72+
sub get_options {
73+
$quiet = !-t STDERR; # quiet by default if STDERR is not a terminal
74+
GetOptions(
75+
help => sub { pod2usage(-verbose => 2, -noperldoc => 1, -exitval => 0) },
76+
quiet => \$quiet,
77+
fix => \$fix,
78+
) or pod2usage(-verbose => 1, -noperldoc => 1, -exitval => 2);
79+
if (@ARGV) {
80+
$root = realpath($ARGV[0]);
81+
} else {
82+
chomp($root = `git rev-parse --show-toplevel`);
83+
exit 2 if $?;
84+
}
85+
die qq(No such directory "$root"\n) if !-d $root;
86+
}
87+
88+
# Open a temporary source file with $suffix, editing $file, removing #include "..."
89+
# lines and replacing #include "$header" with #include "$hdr"
90+
#
91+
# Returns an object which stringifies to filename and deletes it when destroyed
92+
sub open_temp_src {
93+
my ($file, $suffix, $header, $hdr) = @_;
94+
my $tmp = File::Temp->new(SUFFIX => $suffix);
95+
open my $f, "<", $file or die "Internal error: Cannot open $file: $!\n";
96+
while (<$f>) {
97+
if (my ($inc) = /^\s*#\s*include\s*"([^"]*)"/) {
98+
if (defined($hdr) && basename($inc) eq $header) {
99+
print $tmp qq(#include "$hdr"\n) ;
100+
} else {
101+
print $tmp "\n";
102+
}
103+
next;
104+
}
105+
print $tmp $_;
106+
}
107+
close $tmp;
108+
return $tmp;
109+
}
110+
111+
# Check a source file
112+
sub check_file {
113+
my $file = $_;
114+
my $fullpath = realpath($file);
115+
my $dir = dirname($fullpath);
116+
117+
my ($suffix) = /(\.(?:[^.]+))$/
118+
or die "Internal error: Cannot determine file suffix: $fullpath\n";
119+
120+
# Progress dots
121+
print STDERR '.' if !$quiet;
122+
123+
(my $header = $file) =~ s/\Q$suffix\E$/.h/;
124+
125+
# determine language to force if file extension has ambiguous language
126+
my $lang = "";
127+
if ($suffix eq ".h" || $suffix eq ".c") {
128+
open my $f, "<", $fullpath or die "Cannot open $fullpath: $!\n";
129+
$lang = "-x c";
130+
while (<$f>) {
131+
s/\s//g;
132+
if (/\bstd::/ || /^#include<\w+>/ || /^template<.*>/ ||
133+
/^(class|virtual|using|namespace)\b/ || /^(public|private|protected):/) {
134+
$lang = "-x c++";
135+
last;
136+
}
137+
}
138+
}
139+
140+
for my $tries (0..4) {
141+
my ($hdr, $src);
142+
143+
# If this is not a header file, create a modified header file of the
144+
# same prefix if it exists, and #include the modified header file in
145+
# the modified C/C++ file.
146+
#
147+
# If this a header file, create a modified header file
148+
if ($suffix ne ".h") {
149+
$hdr = open_temp_src($header, ".h") if -e $header;
150+
$src = open_temp_src($file, $suffix, $header, $hdr);
151+
} else {
152+
$src = open_temp_src($header, ".h");
153+
}
154+
155+
-e "$src" or die "Internal error: File $src does not exist\n";
156+
-e "$hdr" or die "Internal error: File $hdr does not exist\n" if $hdr;
157+
158+
# GCC command to generate warnings about missing headers
159+
my $cmd = "g++ $lang -S -o /dev/null '$src' 2>&1";
160+
161+
# Parse the GCC output
162+
open my $gcc, "-|", $cmd or die "Could not execute $cmd: $!\n";
163+
my (@src, %hit, %edits);
164+
165+
LINE: while (<$gcc>) {
166+
s:$hdr:$dir/$header: if $hdr;
167+
s:$src:$dir/$file:;
168+
push @src, $_;
169+
170+
# A +++ at the beginning of a line followed by an #include indicates
171+
# A missing system header which needs to be added
172+
if (my ($inc) = /^\s*\+\+\+ \|\+(#include <[^>]+>)$/) {
173+
$inc .= "\n";
174+
# Don't handle the same missing #include twice in the same file
175+
if (!$hit{$inc}++) {
176+
# Find the filename GCC is complaining about and mark it with
177+
# the #include, merging duplicates
178+
for (@src[max($#src-4, 0) .. $#src]) {
179+
180+
if (my ($editfile) = /^([^:]+):\d+:\d+: \w/) {
181+
# skip the edit file if it is not under $root
182+
next LINE if substr(abs2rel($editfile, $root), 0, 3) eq "../";
183+
next LINE if realpath($editfile) ne $fullpath;
184+
++$errors if !$tries && !exists $edits{$editfile};
185+
if (!$edits{$editfile}{$inc}++) {
186+
# Print the lines of compiler output of the #include warning
187+
print "\n", "-" x 119, "\n$fullpath\n",
188+
@src[max($#src-5, 0) .. $#src];
189+
}
190+
next LINE;
191+
}
192+
}
193+
die "Internal error: GCC output not recognized:\n",
194+
@src[max($#src-5, 0) .. $#src];
195+
}
196+
}
197+
}
198+
199+
return if !$fix or !grep keys %{$edits{$_}}, keys %edits;
200+
201+
# Remove the temporary files
202+
undef $hdr;
203+
undef $src;
204+
205+
# Go through all files marked for editing
206+
for my $editfile (keys %edits) {
207+
# The list of #include <...> which need to be added to the file
208+
my @inc = keys %{$edits{$editfile}};
209+
210+
# The complete file to edit
211+
my @prog = do {
212+
open my $fh, "<", $editfile
213+
or die "Internal error: Cannot open $editfile: $!\n";
214+
<$fh>
215+
};
216+
217+
# Scan the program, building @new from @prog and @inc
218+
# Several scans are made with different matching criteria,
219+
# and the first match wins
220+
my @new;
221+
222+
# Put #include before first #include<> line if it exists
223+
if (@inc) {
224+
undef @new;
225+
for (@prog) {
226+
if (@inc) {
227+
# Ignore #if/#ifdef blocks
228+
unless (/^\s*#\s*if(def)?\b/ .. /^\s*#\s*endif\b/) {
229+
@inc = splice @new, @new, 0, @inc if /^\s*#\s*include\s*</;
230+
}
231+
}
232+
push @new, $_;
233+
}
234+
}
235+
236+
# Put #include after the last #include in the file
237+
if (@inc) {
238+
my $insert;
239+
for my $i (0..$#prog) {
240+
local $_ = $prog[$i];
241+
# Ignore #if/#ifdef blocks
242+
unless (/^\s*#\s*if(def)?\b/ .. /^\s*#\s*endif\b/) {
243+
# bail out early if certain declarations occur
244+
last if /^\s*((class|struct|namespace|template)\b|extern\s*"C")/;
245+
$insert = $i+1 if /^\s*#\s*include\b/;
246+
}
247+
}
248+
if (defined $insert) {
249+
@new = @prog;
250+
@inc = splice @new, $insert, 0, "\n", @inc;
251+
}
252+
}
253+
254+
# Put #include before first class/struct/namespace/extern declaration
255+
if (@inc) {
256+
undef @new;
257+
for (@prog) {
258+
if (@inc) {
259+
# Ignore #if/#ifdef blocks
260+
unless (/^\s*#\s*if(def)?\b/ .. /^\s*#\s*endif\b/) {
261+
@inc = splice @new, @new, 0, @inc, "\n"
262+
if /^\s*(class|struct|namespace|template|extern)\b/;
263+
}
264+
}
265+
push @new, $_;
266+
}
267+
}
268+
269+
# Put #include after any comments at the beginning of the file
270+
if (@inc) {
271+
undef @new;
272+
for (@prog) {
273+
if (@inc) {
274+
unless ((m:^\s*/\*: .. m:\*/\s*$:) || m:^\s*//:) {
275+
@inc = splice @new, @new, 0, "\n", @inc;
276+
push @new, "\n" if /\S/;
277+
}
278+
}
279+
push @new, $_;
280+
}
281+
}
282+
283+
# Sort contiguous blocks of #include <...>
284+
my $start;
285+
for (my $i = 0; $i <= $#new; $i++) {
286+
if ($new[$i] =~ /^\s*#\s*include\s*</) {
287+
$start //= $i;
288+
} elsif (defined $start) {
289+
@new[$start .. $i-1] = sort @new[$start .. $i-1];
290+
undef $start;
291+
}
292+
}
293+
@new[$start .. $#new] = sort @new[$start .. $#new] if defined $start;
294+
295+
# Output the new file
296+
open my $fh, ">", $editfile
297+
or die "Internal error: Cannot open $editfile: $!\n";
298+
print $fh @new;
299+
}
300+
}
301+
302+
print STDERR <<EOF;
303+
304+
Too many repeated edits of
305+
306+
$fullpath
307+
308+
Something is not working to eliminate the errors.
309+
310+
This may occur if the only #include in a file is guarded by #ifdef/#if, and
311+
this script cannot figure out where to insert the new #include. Moving it to
312+
outside the #ifdef/#if may fix the error and provide a clear insertion point
313+
for future runs of this script.
314+
315+
This script adds #includes next to existing #includes, so adding one for the
316+
first time should make it work.
317+
318+
319+
EOF
320+
exit 2;
321+
}
322+
323+
=pod
324+
325+
=head1 NAME
326+
327+
test-includes.pl - Test C/C++ sources for system #include files necessary to compile each source file without depending on other source files #include'ing the required headers.
328+
329+
=head1 SYNOPSIS
330+
331+
test-includes.pl [ options ] [ rootdir ]
332+
333+
=head1 OPTIONS
334+
335+
=over
336+
337+
=item B<--fix>
338+
339+
Fix the files, adding suggested #include lines. Changes will need to be reviewed and tested, and added to a Git commit.
340+
341+
=item B<--quiet>
342+
343+
Do not print progress dots on terminals
344+
345+
=item B<--help>
346+
347+
This help message
348+
349+
=back
350+
351+
=head1 DESCRIPTION
352+
353+
Looks at C/C++ files in the root directory and its subdirectories, looking for missing #include <> directives for system headers.
354+
355+
For example, if std::vector is used in a file, then #include <vector> should appear.
356+
357+
The .git and external directories, and any files/directories ignored by Git, are not scanned.
358+
359+
If no root directory is specified, the root directory of the Git respository in the current working directory is used.
360+
361+
=head1 RETURN VALUE
362+
363+
=over
364+
365+
=item 0 on success
366+
367+
=item 1 if there are missing #includes
368+
369+
=item >1 if errors occurred during testing and/or fixing
370+
371+
=back
372+
373+
=head1 LIMITATIONS
374+
375+
GCC needs to be installed and g++ needs to be in the PATH. This tool uses GCC-specific warnings which are not available on Clang.
376+
377+
=cut

src/sst/core/baseComponent.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
#include "sst/core/statapi/statengine.h"
2727
#include "sst/core/warnmacros.h"
2828

29+
#include <cstdint>
2930
#include <functional>
3031
#include <map>
32+
#include <set>
3133
#include <string>
3234
#include <vector>
3335

0 commit comments

Comments
 (0)