Skip to content

Commit 621db59

Browse files
bob-wilsonIanWood1
authored andcommitted
[Clang] Add new -header-include-filtering=direct-per-file option (llvm#137087)
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 87fb28f commit 621db59

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,49 @@ 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 = llvm::DenseMap<FileEntryRef, SmallVector<FileEntryRef>>;
120+
DependencyMap Dependencies;
121+
122+
public:
123+
HeaderIncludesDirectPerFileCallback(const Preprocessor *PP,
124+
raw_ostream *OutputFile_,
125+
bool OwnsOutputFile_)
126+
: SM(PP->getSourceManager()), HSI(PP->getHeaderSearchInfo()),
127+
OutputFile(OutputFile_), OwnsOutputFile(OwnsOutputFile_) {}
128+
129+
~HeaderIncludesDirectPerFileCallback() override {
130+
if (OwnsOutputFile)
131+
delete OutputFile;
132+
}
133+
134+
HeaderIncludesDirectPerFileCallback(
135+
const HeaderIncludesDirectPerFileCallback &) = delete;
136+
HeaderIncludesDirectPerFileCallback &
137+
operator=(const HeaderIncludesDirectPerFileCallback &) = delete;
138+
139+
void EndOfMainFile() override;
140+
141+
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
142+
StringRef FileName, bool IsAngled,
143+
CharSourceRange FilenameRange,
144+
OptionalFileEntryRef File, StringRef SearchPath,
145+
StringRef RelativePath, const Module *SuggestedModule,
146+
bool ModuleImported,
147+
SrcMgr::CharacteristicKind FileType) override;
148+
149+
void moduleImport(SourceLocation ImportLoc, ModuleIdPath Path,
150+
const Module *Imported) override;
151+
};
109152
}
110153

111154
static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename,
@@ -192,14 +235,21 @@ void clang::AttachHeaderIncludeGen(Preprocessor &PP,
192235
MSStyle));
193236
break;
194237
}
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));
238+
case HIFMT_JSON:
239+
switch (DepOpts.HeaderIncludeFiltering) {
240+
default:
241+
llvm_unreachable("Unknown HeaderIncludeFilteringKind enum");
242+
case HIFIL_Only_Direct_System:
243+
PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>(
244+
&PP, OutputFile, OwnsOutputFile));
245+
break;
246+
case HIFIL_Direct_Per_File:
247+
PP.addPPCallbacks(std::make_unique<HeaderIncludesDirectPerFileCallback>(
248+
&PP, OutputFile, OwnsOutputFile));
249+
break;
250+
}
200251
break;
201252
}
202-
}
203253
}
204254

205255
void HeaderIncludesCallback::FileChanged(SourceLocation Loc,
@@ -322,3 +372,81 @@ void HeaderIncludesJSONCallback::FileSkipped(
322372

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