Skip to content

Commit 40bc83e

Browse files
committed
Add script to automatically detect and fix missing system header #includes
Run the script on the sources Change a couple of files with missing #includes which confused script because it could not figure out where to put them except inside of an #ifdef, which prevented them from working.
1 parent 34384ea commit 40bc83e

Some content is hidden

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

55 files changed

+380
-0
lines changed

scripts/test-includes.pl

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
#!/usr/bin/perl
2+
3+
use strict;
4+
use warnings 'all';
5+
6+
use Cwd qw(realpath);
7+
use File::Basename;
8+
use File::Copy;
9+
use File::Find;
10+
use File::Spec::Functions qw(abs2rel);
11+
use File::Temp;
12+
use FindBin qw($Bin);
13+
use Getopt::Long;
14+
use Pod::Usage;
15+
16+
my $errors = 0;
17+
my $root = realpath("$Bin/..");
18+
my $quiet = !-t STDERR; # quiet by default if STDERR is not a terminal
19+
my $fix = 0;
20+
21+
pod2usage(-verbose => 1, -noperldoc => 1, -exitval => 2) unless GetOptions(
22+
help => sub { pod2usage(-verbose => 2, -noperldoc => 1, -exitval => 0) },
23+
quiet => \$quiet,
24+
fix => \$fix,
25+
);
26+
27+
# Default search directory is root directory
28+
push @ARGV, $root unless @ARGV;
29+
30+
# Confirm the presence of g++ compiler
31+
if (!open my $gcc, "-|", qw(g++ --version)) {
32+
print STDERR <<EOF;
33+
34+
GCC needs to be installed and g++ needs to be in the PATH.
35+
36+
This tool uses GCC-specific warnings which are not available on Clang.
37+
EOF
38+
exit 1;
39+
}
40+
41+
# Scan .h files before .cc files, in case --fix is enabled and fixing the .h
42+
# file will automatically fix the corresponding .cc file
43+
for my $suffix (qw(.h .cc)) {
44+
my $filter = sub {
45+
if (-d) {
46+
# prune directories starting with . (except .), external, and build
47+
$File::Find::prune=1 if /^\../ || /^(?:external|build)$/;
48+
} else {
49+
&check_file if /\Q$suffix\E$/;
50+
}
51+
};
52+
find($filter, @ARGV);
53+
}
54+
55+
print STDERR "\n" unless $quiet;
56+
57+
my $rc = 0;
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+
$rc = 1;
64+
}
65+
} else {
66+
print "\nNo missing #includes found\n\nALL TESTS PASSED\n";
67+
}
68+
exit $rc;
69+
70+
# Open a temporary source file with $suffix, editing $file, removing #include
71+
# "..." lines and replacing #include "$header" with #include "$hdr"
72+
# Returns a File::Temp object which deletes temporary file when destroyed
73+
sub open_temp_src {
74+
my ($file, $suffix, $header, $hdr) = @_;
75+
my $tmp = File::Temp->new(SUFFIX => $suffix);
76+
open my $f, "<", $file or die "Internal error: Cannot open $file: $!\n";
77+
while (<$f>) {
78+
if (my ($inc) = /^\s*#\s*include\s*"([^"]*)"/) {
79+
if (defined($hdr) && basename($inc) eq $header) {
80+
print $tmp qq(#include "$hdr"\n) ;
81+
} else {
82+
print $tmp "\n";
83+
}
84+
next;
85+
}
86+
print $tmp $_;
87+
}
88+
close $tmp;
89+
return $tmp;
90+
}
91+
92+
# Check a source file
93+
sub check_file {
94+
my $file = $_;
95+
my $fullpath = $File::Find::name;
96+
my $dir = $File::Find::dir;
97+
98+
my ($suffix) = /(\.(?:[^.]+))$/
99+
or die "Internal error: Cannot determine file suffix\n";
100+
101+
# Progress dots
102+
print STDERR '.' unless $quiet;
103+
104+
(my $header = $file) =~ s/\Q$suffix\E$/.h/;
105+
106+
for (my $tries = 0; $tries < 3; ++$tries) {
107+
my ($hdr, $src);
108+
109+
# If this is not a header file, create a modified header file of the
110+
# same prefix if it exists, and #include the modified header file in
111+
# modified the .cc file.
112+
#
113+
# If this a header file, create a modified header file
114+
if ($suffix ne ".h") {
115+
$hdr = open_temp_src($header, ".h") if -e $header;
116+
$src = open_temp_src($file, $suffix, $header, $hdr);
117+
} else {
118+
$src = open_temp_src($header, ".h");
119+
}
120+
121+
-e "$src" or die "Internal error: File $src does not exist\n";
122+
123+
# GCC command to generate warnings about missing headers
124+
my $cmd = "g++ -x c++ -S -o /dev/null '$src' 2>&1";
125+
126+
# Parse the GCC output
127+
open my $gcc, "-|", $cmd or die;
128+
my (@lines, %hit, %edits);
129+
130+
while (<$gcc>) {
131+
push @lines, $_;
132+
133+
# A +++ at the beginning of a line followed by an #include indicates
134+
# A missing system header which needs to be added
135+
if (my ($inc) = /^\s*\+\+\+ \|\+(#include <[^>]+>)$/) {
136+
$inc .= "\n";
137+
# Don't handle the same missing #include twice in the same file
138+
if (!$hit{$inc}++) {
139+
# Find the filename GCC is complaining about and mark it with
140+
# the #include, merging duplicates
141+
for (@lines[-6..-1]) {
142+
s:$hdr:$dir/$header: if $hdr;
143+
s:$src:$dir/$file:;
144+
}
145+
146+
for (@lines[-2,-3]) {
147+
if (my ($editfile) = /^([^:]+):\d+:\d+: \w/) {
148+
# skip the edit file if it is not under $root
149+
goto end if substr(abs2rel($editfile, $root), 0, 3) eq "../";
150+
151+
++$errors if !$tries && !exists $edits{$editfile};
152+
if (!$edits{$editfile}{$inc}++) {
153+
# Print the lines of compiler output of the #include warning
154+
print "\n", "-" x 119, "\n$fullpath\n",
155+
@lines[($lines[-5] =~ /\^/ ? -6 : -5) .. -1];
156+
}
157+
goto end;
158+
}
159+
}
160+
die "Internal error: GCC output not recognized:\n$lines[-1]\n";
161+
end:
162+
}
163+
}
164+
}
165+
166+
return if !$fix || !grep keys %{$edits{$_}}, keys %edits;
167+
168+
# Remove the temporary files
169+
undef $hdr;
170+
undef $src;
171+
172+
# Go through all files marked for editing
173+
for my $editfile (keys %edits) {
174+
# The list of #include <...> which need to be added to the file
175+
my @inc = keys %{$edits{$editfile}};
176+
177+
# The complete file to edit
178+
my @prog = do {
179+
open my $fh, "<", $editfile
180+
or die "Internal error: Cannot open $editfile: $!\n";
181+
<$fh>
182+
};
183+
184+
# Scan the program, building @new from @prog and @inc
185+
my @new;
186+
for (@prog) {
187+
# If a #include<> line is found, dump @inc
188+
if (/^\s*#\s*include\s*</) {
189+
push @new, @inc;
190+
@inc=();
191+
}
192+
193+
# If namespace is found at beginning of line, dump @inc
194+
if (/^\s*namespace\b/) {
195+
push @new, @inc, "\n" if @inc;
196+
@inc=();
197+
}
198+
# Push the original program's line to @new
199+
push @new, $_;
200+
}
201+
202+
# If no insertion was performed because no #include<> or namespace was
203+
# found, insert after the last #include or at the beginning of file
204+
if (@inc) {
205+
my $insert = 0;
206+
while ($insert <= $#new) {
207+
last if $new[$insert++] !~ m:^//:;
208+
}
209+
for (my $i = $insert; $i <= $#new; $i++) {
210+
$insert = $i+1 if $new[$i] =~ /^\s*#\s*include\W/;
211+
}
212+
@inc = splice @new, $insert, 0, "\n", @inc;
213+
}
214+
215+
# Sort contiguous blocks of #include <...>
216+
my $start;
217+
for (my $i = 0; $i <= $#new; $i++) {
218+
if ($new[$i] =~ /^\s*#\s*include\s*</) {
219+
$start //= $i;
220+
} elsif (defined $start) {
221+
@new[$start .. $i-1] = sort @new[$start .. $i-1];
222+
undef $start;
223+
}
224+
}
225+
@new[$start .. $#new] = sort @new[$start .. $#new] if defined $start;
226+
227+
# Output the new file
228+
open my $fh, ">", $editfile
229+
or die "Internal error: Cannot open $editfile: $!\n";
230+
print $fh @new;
231+
}
232+
}
233+
die <<EOF;
234+
Too many repeated edits of $fullpath -- something is not working to eliminate the errors.
235+
236+
This may occur if the only #include in a file is guarded by #ifdef/#if, and this script
237+
cannot figure out where to insert the new #include. Moving it to outside the #ifdef/#if
238+
may fix the error and provide a clear insertion point for future runs of this script.
239+
EOF
240+
}
241+
242+
=pod
243+
244+
=head1 NAME
245+
246+
test-includes.pl - Test C++ sources for system #include files necessary to compile each source file without depending on other source files #include'ing the required headers.
247+
248+
=head1 SYNOPSIS
249+
250+
test-includes.pl [ options ] [ path... ]
251+
252+
=head1 OPTIONS
253+
254+
=over 8
255+
256+
=item B<--fix>
257+
258+
Fix the files, adding suggested #include lines
259+
260+
=item B<--quiet>
261+
262+
Do not print progress dots
263+
264+
=item B<--help>
265+
266+
This help message
267+
268+
=back
269+
270+
=head1 DESCRIPTION
271+
272+
Looks at C++ files in the paths and their subdirectories, or the Git repository root directory if no directories are given.
273+
274+
GCC needs to be installed and g++ needs to be in the PATH.
275+
276+
This tool uses GCC-specific warnings which are not available on Clang.

src/sst/core/baseComponent.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "sst/core/statapi/statengine.h"
2828
#include "sst/core/warnmacros.h"
2929

30+
#include <cstdint>
3031
#include <functional>
3132
#include <map>
3233
#include <string>

src/sst/core/cfgoutput/dotConfigOutput.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include "sst/core/config.h"
1818
#include "sst/core/configGraphOutput.h"
1919

20+
#include <cinttypes>
21+
2022
using namespace SST::Core;
2123

2224
DotConfigGraphOutput::DotConfigGraphOutput(const char* path) : ConfigGraphOutput(path) {}

src/sst/core/cfgoutput/jsonConfigOutput.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121

2222
#include "nlohmann/json.hpp"
2323

24+
#include <cstdint>
25+
#include <iomanip>
2426
#include <map>
2527
#include <sstream>
28+
#include <vector>
2629

2730
using namespace SST::Core;
2831
namespace json = ::nlohmann;

src/sst/core/cfgoutput/jsonConfigOutput.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#include "sst/core/configGraph.h"
1717
#include "sst/core/configGraphOutput.h"
1818

19+
#include <map>
20+
#include <string>
21+
1922
namespace SST::Core {
2023

2124
class JSONConfigGraphOutput : public ConfigGraphOutput

src/sst/core/cfgoutput/pythonConfigOutput.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
#include "sst/core/timeConverter.h"
2020
#include "sst/core/timeLord.h"
2121

22+
#include <cinttypes>
23+
#include <cstdint>
24+
#include <cstring>
25+
#include <vector>
26+
2227
using namespace SST::Core;
2328

2429
PythonConfigGraphOutput::PythonConfigGraphOutput(const char* path) : ConfigGraphOutput(path), graph_(nullptr) {}

src/sst/core/componentInfo.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "sst/core/serialization/serialize.h"
1919
#include "sst/core/serialization/serializer.h"
2020

21+
#include <cinttypes>
22+
2123
namespace SST {
2224

2325
ComponentInfo::ComponentInfo(ComponentId_t id, const std::string& name) :

src/sst/core/componentInfo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "sst/core/sst_types.h"
1818
#include "sst/core/timeConverter.h"
1919

20+
#include <cstdint>
2021
#include <functional>
2122
#include <map>
2223
#include <string>

src/sst/core/configBase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "sst/core/sst_types.h"
1616

17+
#include <cstdint>
1718
#include <functional>
1819
#include <getopt.h>
1920
#include <iostream>

src/sst/core/elemLoader.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <cstdio>
2424
#include <cstring>
2525
#include <dirent.h>
26+
#include <ostream>
2627
#include <vector>
2728

2829
#ifdef HAVE_DLFCN_H

0 commit comments

Comments
 (0)