Skip to content

Commit a7195bd

Browse files
committed
[Clang] Add new -header-include-filtering=direct-per-file option
This adds a new filtering option to be used along with the -header-include-format=json option. The existing "only-direct-system" filtering option is missing some things: - It does not show module imports. - It does not show includes of non-system headers. This new "direct-per-file" filtering has a separate entry in the JSON output for each non-system source file, showing the direct includes and imports from that file. You can use this to see uses of non-system headers, and also find the paths through non-system headers that lead up to module imports and system headers. Modules are identified here by their modulemap files.
1 parent 4f36ada commit a7195bd

File tree

5 files changed

+153
-10
lines changed

5 files changed

+153
-10
lines changed

clang/include/clang/Basic/HeaderInclude.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,14 @@ enum HeaderIncludeFormatKind { HIFMT_None, HIFMT_Textual, HIFMT_JSON };
2323

2424
/// Whether header information is filtered or not. If HIFIL_Only_Direct_System
2525
/// is used, only information on system headers directly included from
26-
/// non-system headers is emitted.
27-
enum HeaderIncludeFilteringKind { HIFIL_None, HIFIL_Only_Direct_System };
26+
/// non-system files is emitted. The HIFIL_Direct_Per_File filtering shows the
27+
/// direct imports and includes for each non-system source and header file
28+
/// separately.
29+
enum HeaderIncludeFilteringKind {
30+
HIFIL_None,
31+
HIFIL_Only_Direct_System,
32+
HIFIL_Direct_Per_File
33+
};
2834

2935
inline HeaderIncludeFormatKind
3036
stringToHeaderIncludeFormatKind(const char *Str) {
@@ -40,6 +46,7 @@ inline bool stringToHeaderIncludeFiltering(const char *Str,
4046
llvm::StringSwitch<std::pair<bool, HeaderIncludeFilteringKind>>(Str)
4147
.Case("none", {true, HIFIL_None})
4248
.Case("only-direct-system", {true, HIFIL_Only_Direct_System})
49+
.Case("direct-per-file", {true, HIFIL_Direct_Per_File})
4350
.Default({false, HIFIL_None});
4451
Kind = P.second;
4552
return P.first;
@@ -64,6 +71,8 @@ headerIncludeFilteringKindToString(HeaderIncludeFilteringKind K) {
6471
return "none";
6572
case HIFIL_Only_Direct_System:
6673
return "only-direct-system";
74+
case HIFIL_Direct_Per_File:
75+
return "direct-per-file";
6776
}
6877
llvm_unreachable("Unknown HeaderIncludeFilteringKind enum");
6978
}

clang/include/clang/Driver/Options.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7793,7 +7793,8 @@ def header_include_format_EQ : Joined<["-"], "header-include-format=">,
77937793
MarshallingInfoEnum<DependencyOutputOpts<"HeaderIncludeFormat">, "HIFMT_Textual">;
77947794
def header_include_filtering_EQ : Joined<["-"], "header-include-filtering=">,
77957795
HelpText<"set the flag that enables filtering header information">,
7796-
Values<"none,only-direct-system">, NormalizedValues<["HIFIL_None", "HIFIL_Only_Direct_System"]>,
7796+
Values<"none,only-direct-system,direct-per-file">,
7797+
NormalizedValues<["HIFIL_None", "HIFIL_Only_Direct_System", "HIFIL_Direct_Per_File"]>,
77977798
MarshallingInfoEnum<DependencyOutputOpts<"HeaderIncludeFiltering">, "HIFIL_None">;
77987799
def show_includes : Flag<["--"], "show-includes">,
77997800
HelpText<"Print cl.exe style /showIncludes to stdout">;

clang/lib/Frontend/HeaderIncludeGen.cpp

Lines changed: 134 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,50 @@ class HeaderIncludesJSONCallback : public PPCallbacks {
106106
void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
107107
SrcMgr::CharacteristicKind FileType) override;
108108
};
109+
110+
/// A callback for emitting direct header and module usage information to a
111+
/// file in JSON. The output format is like HeaderIncludesJSONCallback but has
112+
/// an array of separate entries, one for each non-system source file used in
113+
/// the compilation showing only the direct includes and imports from that file.
114+
class HeaderIncludesDirectPerFileCallback : public PPCallbacks {
115+
SourceManager &SM;
116+
HeaderSearch &HSI;
117+
raw_ostream *OutputFile;
118+
bool OwnsOutputFile;
119+
using DependencyMap =
120+
llvm::DenseMap<FileEntryRef, SmallVector<FileEntryRef>>;
121+
DependencyMap Dependencies;
122+
123+
public:
124+
HeaderIncludesDirectPerFileCallback(const Preprocessor *PP,
125+
raw_ostream *OutputFile_,
126+
bool OwnsOutputFile_)
127+
: SM(PP->getSourceManager()), HSI(PP->getHeaderSearchInfo()),
128+
OutputFile(OutputFile_), OwnsOutputFile(OwnsOutputFile_) {}
129+
130+
~HeaderIncludesDirectPerFileCallback() override {
131+
if (OwnsOutputFile)
132+
delete OutputFile;
133+
}
134+
135+
HeaderIncludesDirectPerFileCallback
136+
(const HeaderIncludesDirectPerFileCallback &) = delete;
137+
HeaderIncludesDirectPerFileCallback &
138+
operator=(const HeaderIncludesDirectPerFileCallback &) = delete;
139+
140+
void EndOfMainFile() override;
141+
142+
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
143+
StringRef FileName, bool IsAngled,
144+
CharSourceRange FilenameRange,
145+
OptionalFileEntryRef File, StringRef SearchPath,
146+
StringRef RelativePath, const Module *SuggestedModule,
147+
bool ModuleImported,
148+
SrcMgr::CharacteristicKind FileType) override;
149+
150+
void moduleImport(SourceLocation ImportLoc, ModuleIdPath Path,
151+
const Module *Imported) override;
152+
};
109153
}
110154

111155
static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename,
@@ -192,14 +236,21 @@ void clang::AttachHeaderIncludeGen(Preprocessor &PP,
192236
MSStyle));
193237
break;
194238
}
195-
case HIFMT_JSON: {
196-
assert(DepOpts.HeaderIncludeFiltering == HIFIL_Only_Direct_System &&
197-
"only-direct-system is the only option for filtering");
198-
PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>(
199-
&PP, OutputFile, OwnsOutputFile));
239+
case HIFMT_JSON:
240+
switch (DepOpts.HeaderIncludeFiltering) {
241+
default:
242+
llvm_unreachable("Unknown HeaderIncludeFilteringKind enum");
243+
case HIFIL_Only_Direct_System:
244+
PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>(
245+
&PP, OutputFile, OwnsOutputFile));
246+
break;
247+
case HIFIL_Direct_Per_File:
248+
PP.addPPCallbacks(std::make_unique<HeaderIncludesDirectPerFileCallback>(
249+
&PP, OutputFile, OwnsOutputFile));
250+
break;
251+
}
200252
break;
201253
}
202-
}
203254
}
204255

205256
void HeaderIncludesCallback::FileChanged(SourceLocation Loc,
@@ -322,3 +373,80 @@ void HeaderIncludesJSONCallback::FileSkipped(
322373

323374
IncludedHeaders.push_back(SkippedFile.getName().str());
324375
}
376+
377+
void HeaderIncludesDirectPerFileCallback::EndOfMainFile() {
378+
if (Dependencies.empty())
379+
return;
380+
381+
// Sort the files so that the output does not depend on the DenseMap order.
382+
SmallVector<FileEntryRef> SourceFiles;
383+
for (auto F = Dependencies.begin(), FEnd = Dependencies.end();
384+
F != FEnd; ++F) {
385+
SourceFiles.push_back(F->first);
386+
}
387+
llvm::sort(SourceFiles, [](const FileEntryRef &LHS, const FileEntryRef &RHS) {
388+
return LHS.getUID() < RHS.getUID();
389+
});
390+
391+
std::string Str;
392+
llvm::raw_string_ostream OS(Str);
393+
llvm::json::OStream JOS(OS);
394+
JOS.array([&] {
395+
for (auto S = SourceFiles.begin(), SE = SourceFiles.end(); S != SE; ++S) {
396+
JOS.object([&] {
397+
SmallVector<FileEntryRef> &Deps = Dependencies[*S];
398+
JOS.attribute("source", S->getName().str());
399+
JOS.attributeArray("includes", [&] {
400+
for (unsigned I = 0, N = Deps.size(); I != N; ++I)
401+
JOS.value(Deps[I].getName().str());
402+
});
403+
});
404+
}
405+
});
406+
OS << "\n";
407+
408+
if (OutputFile->get_kind() == raw_ostream::OStreamKind::OK_FDStream) {
409+
llvm::raw_fd_ostream *FDS = static_cast<llvm::raw_fd_ostream *>(OutputFile);
410+
if (auto L = FDS->lock())
411+
*OutputFile << Str;
412+
} else
413+
*OutputFile << Str;
414+
}
415+
416+
void HeaderIncludesDirectPerFileCallback::InclusionDirective(
417+
SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName,
418+
bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File,
419+
StringRef SearchPath, StringRef RelativePath, const Module *SuggestedModule,
420+
bool ModuleImported, SrcMgr::CharacteristicKind FileType) {
421+
if (!File)
422+
return;
423+
424+
SourceLocation Loc = SM.getExpansionLoc(HashLoc);
425+
if (SM.isInSystemHeader(Loc))
426+
return;
427+
OptionalFileEntryRef FromFile = SM.getFileEntryRefForID(SM.getFileID(Loc));
428+
if (!FromFile)
429+
return;
430+
431+
Dependencies[*FromFile].push_back(*File);
432+
}
433+
434+
void HeaderIncludesDirectPerFileCallback::moduleImport(
435+
SourceLocation ImportLoc, ModuleIdPath Path, const Module *Imported) {
436+
if (!Imported)
437+
return;
438+
439+
SourceLocation Loc = SM.getExpansionLoc(ImportLoc);
440+
if (SM.isInSystemHeader(Loc))
441+
return;
442+
OptionalFileEntryRef FromFile = SM.getFileEntryRefForID(SM.getFileID(Loc));
443+
if (!FromFile)
444+
return;
445+
446+
OptionalFileEntryRef ModuleMapFile =
447+
HSI.getModuleMap().getModuleMapFileForUniquing(Imported);
448+
if (!ModuleMapFile)
449+
return;
450+
451+
Dependencies[*FromFile].push_back(*ModuleMapFile);
452+
}

clang/test/Preprocessor/print-header-json.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@
1313
// RUN: env CC_PRINT_HEADERS_FORMAT=json CC_PRINT_HEADERS_FILTERING=only-direct-system CC_PRINT_HEADERS_FILE=%t.txt %clang -fsyntax-only -I %S/Inputs/print-header-json -isystem %S/Inputs/print-header-json/system %s -o /dev/null
1414
// RUN: cat %t.txt | FileCheck %s --check-prefix=SUPPORTED
1515

16+
// RUN: rm %t.txt
17+
// RUN: env CC_PRINT_HEADERS_FORMAT=json CC_PRINT_HEADERS_FILTERING=direct-per-file CC_PRINT_HEADERS_FILE=%t.txt %clang -fsyntax-only -I %S/Inputs/print-header-json -isystem %S/Inputs/print-header-json/system %s -o /dev/null
18+
// RUN: cat %t.txt | FileCheck %s --check-prefix=SUPPORTED_PERFILE
19+
1620
#include "system0.h"
1721
#include "header0.h"
1822
#include "system2.h"
1923

2024
// SUPPORTED: {"source":"{{[^,]*}}print-header-json.c","includes":["{{[^,]*}}system0.h","{{[^,]*}}system3.h","{{[^,]*}}system2.h"]}
25+
// SUPPORTED_PERFILE: [{"source":"{{[^,]*}}print-header-json.c","includes":["{{[^,]*}}system0.h","{{[^,]*}}header0.h","{{[^,]*}}system2.h"]},{"source":"{{[^,]*}}header0.h","includes":["{{[^,]*}}system3.h","{{[^,]*}}header1.h","{{[^,]*}}header2.h"]}]
2126

2227
// UNSUPPORTED0: error: unsupported combination: -header-include-format=textual and -header-include-filtering=only-direct-system
2328
// UNSUPPORTED1: error: unsupported combination: -header-include-format=json and -header-include-filtering=none

clang/tools/driver/driver.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ static bool SetBackdoorDriverOutputsFromEnvVars(Driver &TheDriver) {
171171
if ((TheDriver.CCPrintHeadersFormat == HIFMT_Textual &&
172172
Filtering != HIFIL_None) ||
173173
(TheDriver.CCPrintHeadersFormat == HIFMT_JSON &&
174-
Filtering != HIFIL_Only_Direct_System)) {
174+
Filtering == HIFIL_None)) {
175175
TheDriver.Diag(clang::diag::err_drv_print_header_env_var_combination)
176176
<< EnvVar << FilteringStr;
177177
return false;

0 commit comments

Comments
 (0)