Skip to content

Commit 71d83bb

Browse files
committed
Add -fkeep-system-includes modifier for -E
This option will cause -E to preserve the #include directives for system headers, rather than expanding them into the output. This can greatly reduce the volume of preprocessed source text in a test case, making test case reduction simpler. Note that -fkeep-system-includes is not always appropriate. For example, if the problem you want to reproduce is induced by a system header file, it's better to expand those headers fully. If your source defines symbols that influence the content of a system header (e.g., _POSIX_SOURCE) then -E will eliminate the definition, potentially changing the meaning of the preprocessed source. If you use -isystem to point to non-system headers, for example to suppress warnings in third-party software, those will not be expanded and might make the preprocessed source less useful as a test case.
1 parent 9500616 commit 71d83bb

File tree

12 files changed

+116
-26
lines changed

12 files changed

+116
-26
lines changed

clang/docs/CommandGuide/clang.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,21 @@ Preprocessor Options
684684

685685
Do not search clang's builtin directory for include files.
686686

687+
.. option:: -fkeep-system-includes
688+
689+
Usable only with :option:`-E`. Do not copy the preprocessed content of
690+
"system" headers to the output; instead, preserve the #include directive.
691+
This can greatly reduce the volume of text produced by :option:`-E` which
692+
can be helpful when trying to produce a "small" reproduceable test case.
693+
694+
This option does not guarantee reproduceability, however. If the including
695+
source defines preprocessor symbols that influence the behavior of system
696+
headers (for example, ``_XOPEN_SOURCE``) the operation of :option:`-E` will
697+
remove that definition and thus can change the semantics of the included
698+
header. Also, using a different version of the system headers (especially a
699+
different version of the STL) may result in different behavior. Always verify
700+
the preprocessed file by compiling it separately.
701+
687702

688703
ENVIRONMENT
689704
-----------

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,18 @@ Non-comprehensive list of changes in this release
152152

153153
New Compiler Flags
154154
------------------
155+
155156
* ``-fverify-intermediate-code`` and its complement ``-fno-verify-intermediate-code``.
156157
Enables or disables verification of the generated LLVM IR.
157158
Users can pass this to turn on extra verification to catch certain types of
158159
compiler bugs at the cost of extra compile time.
159160
Since enabling the verifier adds a non-trivial cost of a few percent impact on
160161
build times, it's disabled by default, unless your LLVM distribution itself is
161162
compiled with runtime checks enabled.
163+
* ``-fkeep-system-includes`` modifies the behavior of the ``-E`` option,
164+
preserving ``#include`` directives for "system" headers instead of copying
165+
the preprocessed text to the output. This can greatly reduce the size of the
166+
preprocessed output, which can be helpful when trying to reduce a test case.
162167

163168
Deprecated Compiler Flags
164169
-------------------------

clang/include/clang/Basic/DiagnosticDriverKinds.td

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ def err_drv_invalid_Xopenmp_target_with_args : Error<
182182
"invalid -Xopenmp-target argument: '%0', options requiring arguments are unsupported">;
183183
def err_drv_argument_only_allowed_with : Error<
184184
"invalid argument '%0' only allowed with '%1'">;
185-
def err_drv_minws_unsupported_input_type : Error<
186-
"'-fminimize-whitespace' invalid for input of type %0">;
185+
def err_drv_opt_unsupported_input_type : Error<
186+
"'%0' invalid for input of type %1">;
187187
def err_drv_amdgpu_ieee_without_no_honor_nans : Error<
188188
"invalid argument '-mno-amdgpu-ieee' only allowed with relaxed NaN handling">;
189189
def err_drv_argument_not_allowed_with : Error<

clang/include/clang/Driver/Options.td

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2519,6 +2519,17 @@ defm minimize_whitespace : BoolFOption<"minimize-whitespace",
25192519
"whitespace such that two files with only formatting changes are "
25202520
"equal.\n\nOnly valid with -E on C-like inputs and incompatible "
25212521
"with -traditional-cpp.">, NegFlag<SetFalse>>;
2522+
defm keep_system_includes : BoolFOption<"keep-system-includes",
2523+
PreprocessorOutputOpts<"KeepSystemIncludes">, DefaultFalse,
2524+
PosFlag<SetTrue, [], [ClangOption, CC1Option],
2525+
"Instead of expanding system headers when emitting preprocessor "
2526+
"output, preserve the #include directive. Useful when producing "
2527+
"preprocessed output for test case reduction. May produce incorrect "
2528+
"output if preprocessor symbols that control the included content "
2529+
"(e.g. _XOPEN_SOURCE) are defined in the including source file. The "
2530+
"portability of the resulting source to other compilation environments "
2531+
"is not guaranteed.\n\nOnly valid with -E.">,
2532+
NegFlag<SetFalse>>;
25222533

25232534
def ffreestanding : Flag<["-"], "ffreestanding">, Group<f_Group>,
25242535
Visibility<[ClangOption, CC1Option]>,

clang/include/clang/Frontend/PreprocessorOutputOptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class PreprocessorOutputOptions {
2626
unsigned RewriteImports : 1; ///< Include contents of transitively-imported modules.
2727
unsigned MinimizeWhitespace : 1; ///< Ignore whitespace from input.
2828
unsigned DirectivesOnly : 1; ///< Process directives but do not expand macros.
29+
unsigned KeepSystemIncludes : 1; ///< Do not expand system headers.
2930

3031
public:
3132
PreprocessorOutputOptions() {
@@ -40,6 +41,7 @@ class PreprocessorOutputOptions {
4041
RewriteImports = 0;
4142
MinimizeWhitespace = 0;
4243
DirectivesOnly = 0;
44+
KeepSystemIncludes = 0;
4345
}
4446
};
4547

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ using namespace llvm::opt;
6868
static void CheckPreprocessingOptions(const Driver &D, const ArgList &Args) {
6969
if (Arg *A = Args.getLastArg(clang::driver::options::OPT_C, options::OPT_CC,
7070
options::OPT_fminimize_whitespace,
71-
options::OPT_fno_minimize_whitespace)) {
71+
options::OPT_fno_minimize_whitespace,
72+
options::OPT_fkeep_system_includes,
73+
options::OPT_fno_keep_system_includes)) {
7274
if (!Args.hasArg(options::OPT_E) && !Args.hasArg(options::OPT__SLASH_P) &&
7375
!Args.hasArg(options::OPT__SLASH_EP) && !D.CCCIsCPP()) {
7476
D.Diag(clang::diag::err_drv_argument_only_allowed_with)
@@ -6717,11 +6719,21 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
67176719
options::OPT_fno_minimize_whitespace, false)) {
67186720
types::ID InputType = Inputs[0].getType();
67196721
if (!isDerivedFromC(InputType))
6720-
D.Diag(diag::err_drv_minws_unsupported_input_type)
6721-
<< types::getTypeName(InputType);
6722+
D.Diag(diag::err_drv_opt_unsupported_input_type)
6723+
<< "-fminimize-whitespace" << types::getTypeName(InputType);
67226724
CmdArgs.push_back("-fminimize-whitespace");
67236725
}
67246726

6727+
// -fno-keep-system-includes is default.
6728+
if (Args.hasFlag(options::OPT_fkeep_system_includes,
6729+
options::OPT_fno_keep_system_includes, false)) {
6730+
types::ID InputType = Inputs[0].getType();
6731+
if (!isDerivedFromC(InputType))
6732+
D.Diag(diag::err_drv_opt_unsupported_input_type)
6733+
<< "-fkeep-system-includes" << types::getTypeName(InputType);
6734+
CmdArgs.push_back("-fkeep-system-includes");
6735+
}
6736+
67256737
// -fms-extensions=0 is default.
67266738
if (Args.hasFlag(options::OPT_fms_extensions, options::OPT_fno_ms_extensions,
67276739
IsWindowsMSVC))

clang/lib/Frontend/PrintPreprocessedOutput.cpp

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ class PrintPPOutputPPCallbacks : public PPCallbacks {
9797
bool IsFirstFileEntered;
9898
bool MinimizeWhitespace;
9999
bool DirectivesOnly;
100+
bool KeepSystemIncludes;
101+
raw_ostream *OrigOS;
102+
std::unique_ptr<llvm::raw_null_ostream> NullOS;
100103

101104
Token PrevTok;
102105
Token PrevPrevTok;
@@ -105,19 +108,22 @@ class PrintPPOutputPPCallbacks : public PPCallbacks {
105108
PrintPPOutputPPCallbacks(Preprocessor &pp, raw_ostream *os, bool lineMarkers,
106109
bool defines, bool DumpIncludeDirectives,
107110
bool UseLineDirectives, bool MinimizeWhitespace,
108-
bool DirectivesOnly)
111+
bool DirectivesOnly, bool KeepSystemIncludes)
109112
: PP(pp), SM(PP.getSourceManager()), ConcatInfo(PP), OS(os),
110113
DisableLineMarkers(lineMarkers), DumpDefines(defines),
111114
DumpIncludeDirectives(DumpIncludeDirectives),
112115
UseLineDirectives(UseLineDirectives),
113-
MinimizeWhitespace(MinimizeWhitespace), DirectivesOnly(DirectivesOnly) {
116+
MinimizeWhitespace(MinimizeWhitespace), DirectivesOnly(DirectivesOnly),
117+
KeepSystemIncludes(KeepSystemIncludes), OrigOS(os) {
114118
CurLine = 0;
115119
CurFilename += "<uninit>";
116120
EmittedTokensOnThisLine = false;
117121
EmittedDirectiveOnThisLine = false;
118122
FileType = SrcMgr::C_User;
119123
Initialized = false;
120124
IsFirstFileEntered = false;
125+
if (KeepSystemIncludes)
126+
NullOS = std::make_unique<llvm::raw_null_ostream>();
121127

122128
PrevTok.startToken();
123129
PrevPrevTok.startToken();
@@ -350,6 +356,10 @@ void PrintPPOutputPPCallbacks::FileChanged(SourceLocation Loc,
350356

351357
CurLine = NewLine;
352358

359+
// In KeepSystemIncludes mode, redirect OS as needed.
360+
if (KeepSystemIncludes && (isSystem(FileType) != isSystem(NewFileType)))
361+
OS = isSystem(FileType) ? OrigOS : NullOS.get();
362+
353363
CurFilename.clear();
354364
CurFilename += UserLoc.getFilename();
355365
FileType = NewFileType;
@@ -394,14 +404,16 @@ void PrintPPOutputPPCallbacks::InclusionDirective(
394404
StringRef SearchPath, StringRef RelativePath, const Module *Imported,
395405
SrcMgr::CharacteristicKind FileType) {
396406
// In -dI mode, dump #include directives prior to dumping their content or
397-
// interpretation.
398-
if (DumpIncludeDirectives) {
407+
// interpretation. Similar for -fkeep-system-includes.
408+
if (DumpIncludeDirectives || (KeepSystemIncludes && isSystem(FileType))) {
399409
MoveToLine(HashLoc, /*RequireStartOfLine=*/true);
400410
const std::string TokenText = PP.getSpelling(IncludeTok);
401411
assert(!TokenText.empty());
402412
*OS << "#" << TokenText << " "
403413
<< (IsAngled ? '<' : '"') << FileName << (IsAngled ? '>' : '"')
404-
<< " /* clang -E -dI */";
414+
<< " /* clang -E "
415+
<< (DumpIncludeDirectives ? "-dI" : "-fkeep-system-includes")
416+
<< " */";
405417
setEmittedDirectiveOnThisLine();
406418
}
407419

@@ -412,7 +424,8 @@ void PrintPPOutputPPCallbacks::InclusionDirective(
412424
case tok::pp_import:
413425
case tok::pp_include_next:
414426
MoveToLine(HashLoc, /*RequireStartOfLine=*/true);
415-
*OS << "#pragma clang module import " << Imported->getFullModuleName(true)
427+
*OS << "#pragma clang module import "
428+
<< Imported->getFullModuleName(true)
416429
<< " /* clang -E: implicit import for "
417430
<< "#" << PP.getSpelling(IncludeTok) << " "
418431
<< (IsAngled ? '<' : '"') << FileName << (IsAngled ? '>' : '"')
@@ -794,8 +807,7 @@ struct UnknownPragmaHandler : public PragmaHandler {
794807

795808

796809
static void PrintPreprocessedTokens(Preprocessor &PP, Token &Tok,
797-
PrintPPOutputPPCallbacks *Callbacks,
798-
raw_ostream *OS) {
810+
PrintPPOutputPPCallbacks *Callbacks) {
799811
bool DropComments = PP.getLangOpts().TraditionalCPP &&
800812
!PP.getCommentRetentionState();
801813

@@ -863,22 +875,22 @@ static void PrintPreprocessedTokens(Preprocessor &PP, Token &Tok,
863875
// components. We don't have a good way to round-trip those.
864876
Module *M = reinterpret_cast<Module *>(Tok.getAnnotationValue());
865877
std::string Name = M->getFullModuleName();
866-
OS->write(Name.data(), Name.size());
878+
Callbacks->OS->write(Name.data(), Name.size());
867879
Callbacks->HandleNewlinesInToken(Name.data(), Name.size());
868880
} else if (Tok.isAnnotation()) {
869881
// Ignore annotation tokens created by pragmas - the pragmas themselves
870882
// will be reproduced in the preprocessed output.
871883
PP.Lex(Tok);
872884
continue;
873885
} else if (IdentifierInfo *II = Tok.getIdentifierInfo()) {
874-
*OS << II->getName();
886+
*Callbacks->OS << II->getName();
875887
} else if (Tok.isLiteral() && !Tok.needsCleaning() &&
876888
Tok.getLiteralData()) {
877-
OS->write(Tok.getLiteralData(), Tok.getLength());
889+
Callbacks->OS->write(Tok.getLiteralData(), Tok.getLength());
878890
} else if (Tok.getLength() < std::size(Buffer)) {
879891
const char *TokPtr = Buffer;
880892
unsigned Len = PP.getSpelling(Tok, TokPtr);
881-
OS->write(TokPtr, Len);
893+
Callbacks->OS->write(TokPtr, Len);
882894

883895
// Tokens that can contain embedded newlines need to adjust our current
884896
// line number.
@@ -895,7 +907,7 @@ static void PrintPreprocessedTokens(Preprocessor &PP, Token &Tok,
895907
}
896908
} else {
897909
std::string S = PP.getSpelling(Tok);
898-
OS->write(S.data(), S.size());
910+
Callbacks->OS->write(S.data(), S.size());
899911

900912
// Tokens that can contain embedded newlines need to adjust our current
901913
// line number.
@@ -970,7 +982,7 @@ void clang::DoPrintPreprocessedInput(Preprocessor &PP, raw_ostream *OS,
970982
PrintPPOutputPPCallbacks *Callbacks = new PrintPPOutputPPCallbacks(
971983
PP, OS, !Opts.ShowLineMarkers, Opts.ShowMacros,
972984
Opts.ShowIncludeDirectives, Opts.UseLineDirectives,
973-
Opts.MinimizeWhitespace, Opts.DirectivesOnly);
985+
Opts.MinimizeWhitespace, Opts.DirectivesOnly, Opts.KeepSystemIncludes);
974986

975987
// Expand macros in pragmas with -fms-extensions. The assumption is that
976988
// the majority of pragmas in such a file will be Microsoft pragmas.
@@ -1028,7 +1040,7 @@ void clang::DoPrintPreprocessedInput(Preprocessor &PP, raw_ostream *OS,
10281040
} while (true);
10291041

10301042
// Read all the preprocessed tokens, printing them out to the stream.
1031-
PrintPreprocessedTokens(PP, Tok, Callbacks, OS);
1043+
PrintPreprocessedTokens(PP, Tok, Callbacks);
10321044
*OS << '\n';
10331045

10341046
// Remove the handlers we just added to leave the preprocessor in a sane state
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int dashE_1;
2+
#include <a.h>
3+
int dashE_2;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int a_1;
2+
#include <b.h>
3+
int a_2;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int b_1;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: mkdir %t.dir
2+
// RUN: %clang_cc1 -E -fkeep-system-includes -I %S/Inputs/dashE -isystem %S/Inputs/dashE/sys %s | FileCheck %s
3+
4+
int main_1 = 1;
5+
#include <a.h>
6+
int main_2 = 1;
7+
#include "dashE.h"
8+
int main_3 = 1;
9+
10+
// CHECK: main_1
11+
// CHECK: #include <a.h>
12+
// CHECK-NOT: a_1
13+
// CHECK-NOT: a_2
14+
// CHECK-NOT: b.h
15+
// CHECK: main_2
16+
// CHECK-NOT: #include "dashE.h"
17+
// CHECK: dashE_1
18+
// CHECK: #include <a.h>
19+
// CHECK-NOT: a_1
20+
// CHECK-NOT: a_2
21+
// CHECK-NOT: b.h
22+
// CHECK: dashE_2
23+
// CHECK: main_3
Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
// RUN: not %clang -c -fminimize-whitespace %s 2>&1 | FileCheck %s --check-prefix=ON
2-
// ON: error: invalid argument '-fminimize-whitespace' only allowed with '-E'
1+
// RUN: not %clang -c -fminimize-whitespace %s 2>&1 | FileCheck %s --check-prefix=ON -DOPT=-fminimize-whitespace
2+
// RUN: not %clang -c -fkeep-system-includes %s 2>&1 | FileCheck %s --check-prefix=ON -DOPT=-fkeep-system-includes
3+
// ON: error: invalid argument '[[OPT]]' only allowed with '-E'
34

4-
// RUN: not %clang -c -fno-minimize-whitespace %s 2>&1 | FileCheck %s --check-prefix=OFF
5-
// OFF: error: invalid argument '-fno-minimize-whitespace' only allowed with '-E'
5+
// RUN: not %clang -c -fno-minimize-whitespace %s 2>&1 | FileCheck %s --check-prefix=OFF -DOPT=-fno-minimize-whitespace
6+
// RUN: not %clang -c -fno-keep-system-includes %s 2>&1 | FileCheck %s --check-prefix=OFF -DOPT=-fno-keep-system-includes
7+
// OFF: error: invalid argument '[[OPT]]' only allowed with '-E'
68

7-
// RUN: not %clang -E -fminimize-whitespace -x assembler-with-cpp %s 2>&1 | FileCheck %s --check-prefix=ASM
8-
// ASM: error: '-fminimize-whitespace' invalid for input of type assembler-with-cpp
9+
// RUN: not %clang -E -fminimize-whitespace -x assembler-with-cpp %s 2>&1 | FileCheck %s --check-prefix=ASM -DOPT=-fminimize-whitespace
10+
// RUN: not %clang -E -fkeep-system-includes -x assembler-with-cpp %s 2>&1 | FileCheck %s --check-prefix=ASM -DOPT=-fkeep-system-includes
11+
// ASM: error: '[[OPT]]' invalid for input of type assembler-with-cpp

0 commit comments

Comments
 (0)