clang 22.0.0git
HeaderIncludeGen.cpp
Go to the documentation of this file.
1//===-- HeaderIncludeGen.cpp - Generate Header Includes -------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
14#include "llvm/ADT/SmallString.h"
15#include "llvm/Support/JSON.h"
16#include "llvm/Support/raw_ostream.h"
17using namespace clang;
18
19namespace {
20class HeaderIncludesCallback : public PPCallbacks {
22 raw_ostream *OutputFile;
23 const DependencyOutputOptions &DepOpts;
24 unsigned CurrentIncludeDepth;
25 bool HasProcessedPredefines;
26 bool OwnsOutputFile;
27 bool ShowAllHeaders;
28 bool ShowDepth;
29 bool MSStyle;
30
31public:
32 HeaderIncludesCallback(const Preprocessor *PP, bool ShowAllHeaders_,
33 raw_ostream *OutputFile_,
34 const DependencyOutputOptions &DepOpts,
35 bool OwnsOutputFile_, bool ShowDepth_, bool MSStyle_)
36 : SM(PP->getSourceManager()), OutputFile(OutputFile_), DepOpts(DepOpts),
37 CurrentIncludeDepth(0), HasProcessedPredefines(false),
38 OwnsOutputFile(OwnsOutputFile_), ShowAllHeaders(ShowAllHeaders_),
39 ShowDepth(ShowDepth_), MSStyle(MSStyle_) {}
40
41 ~HeaderIncludesCallback() override {
42 if (OwnsOutputFile)
43 delete OutputFile;
44 }
45
46 HeaderIncludesCallback(const HeaderIncludesCallback &) = delete;
47 HeaderIncludesCallback &operator=(const HeaderIncludesCallback &) = delete;
48
49 void FileChanged(SourceLocation Loc, FileChangeReason Reason,
51 FileID PrevFID) override;
52
53 void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
55
56private:
57 bool ShouldShowHeader(SrcMgr::CharacteristicKind HeaderType) {
58 if (!DepOpts.IncludeSystemHeaders && isSystem(HeaderType))
59 return false;
60
61 // Show the current header if we are (a) past the predefines, or (b) showing
62 // all headers and in the predefines at a depth past the initial file and
63 // command line buffers.
64 return (HasProcessedPredefines ||
65 (ShowAllHeaders && CurrentIncludeDepth > 2));
66 }
67};
68
69/// A callback for emitting header usage information to a file in JSON. Each
70/// line in the file is a JSON object that includes the source file name and
71/// the list of headers directly or indirectly included from it. For example:
72///
73/// {"source":"/tmp/foo.c",
74/// "includes":["/usr/include/stdio.h", "/usr/include/stdlib.h"]}
75///
76/// To reduce the amount of data written to the file, we only record system
77/// headers that are directly included from a file that isn't in the system
78/// directory.
79class HeaderIncludesJSONCallback : public PPCallbacks {
81 raw_ostream *OutputFile;
82 bool OwnsOutputFile;
83 SmallVector<std::string, 16> IncludedHeaders;
84
85public:
86 HeaderIncludesJSONCallback(const Preprocessor *PP, raw_ostream *OutputFile_,
87 bool OwnsOutputFile_)
88 : SM(PP->getSourceManager()), OutputFile(OutputFile_),
89 OwnsOutputFile(OwnsOutputFile_) {}
90
91 ~HeaderIncludesJSONCallback() override {
92 if (OwnsOutputFile)
93 delete OutputFile;
94 }
95
96 HeaderIncludesJSONCallback(const HeaderIncludesJSONCallback &) = delete;
97 HeaderIncludesJSONCallback &
98 operator=(const HeaderIncludesJSONCallback &) = delete;
99
100 void EndOfMainFile() override;
101
102 void FileChanged(SourceLocation Loc, FileChangeReason Reason,
104 FileID PrevFID) override;
105
106 void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
108};
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.
114class HeaderIncludesDirectPerFileCallback : public PPCallbacks {
116 HeaderSearch &HSI;
117 raw_ostream *OutputFile;
118 bool OwnsOutputFile;
119 using DependencyMap = llvm::DenseMap<FileEntryRef, SmallVector<FileEntryRef>>;
120 DependencyMap Dependencies;
121
122public:
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,
148
150 const Module *Imported) override;
151};
152}
153
154static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename,
155 bool ShowDepth, unsigned CurrentIncludeDepth,
156 bool MSStyle) {
157 // Write to a temporary string to avoid unnecessary flushing on errs().
158 SmallString<512> Pathname(Filename);
159 if (!MSStyle)
160 Lexer::Stringify(Pathname);
161
163 if (MSStyle)
164 Msg += "Note: including file:";
165
166 if (ShowDepth) {
167 // The main source file is at depth 1, so skip one dot.
168 for (unsigned i = 1; i != CurrentIncludeDepth; ++i)
169 Msg += MSStyle ? ' ' : '.';
170
171 if (!MSStyle)
172 Msg += ' ';
173 }
174 Msg += Pathname;
175 Msg += '\n';
176
177 *OutputFile << Msg;
178 OutputFile->flush();
179}
180
182 const DependencyOutputOptions &DepOpts,
183 bool ShowAllHeaders, StringRef OutputPath,
184 bool ShowDepth, bool MSStyle) {
185 raw_ostream *OutputFile = &llvm::errs();
186 bool OwnsOutputFile = false;
187
188 // Choose output stream, when printing in cl.exe /showIncludes style.
189 if (MSStyle) {
190 switch (DepOpts.ShowIncludesDest) {
191 default:
192 llvm_unreachable("Invalid destination for /showIncludes output!");
193 case ShowIncludesDestination::Stderr:
194 OutputFile = &llvm::errs();
195 break;
196 case ShowIncludesDestination::Stdout:
197 OutputFile = &llvm::outs();
198 break;
199 }
200 }
201
202 // Open the output file, if used.
203 if (!OutputPath.empty()) {
204 std::error_code EC;
205 llvm::raw_fd_ostream *OS = new llvm::raw_fd_ostream(
206 OutputPath.str(), EC,
207 llvm::sys::fs::OF_Append | llvm::sys::fs::OF_TextWithCRLF);
208 if (EC) {
209 PP.getDiagnostics().Report(clang::diag::warn_fe_cc_print_header_failure)
210 << EC.message();
211 delete OS;
212 } else {
213 OS->SetUnbuffered();
214 OutputFile = OS;
215 OwnsOutputFile = true;
216 }
217 }
218
219 switch (DepOpts.HeaderIncludeFormat) {
220 case HIFMT_None:
221 llvm_unreachable("unexpected header format kind");
222 case HIFMT_Textual: {
223 assert(DepOpts.HeaderIncludeFiltering == HIFIL_None &&
224 "header filtering is currently always disabled when output format is"
225 "textual");
226 // Print header info for extra headers, pretending they were discovered by
227 // the regular preprocessor. The primary use case is to support proper
228 // generation of Make / Ninja file dependencies for implicit includes, such
229 // as sanitizer ignorelists. It's only important for cl.exe compatibility,
230 // the GNU way to generate rules is -M / -MM / -MD / -MMD.
231 for (const auto &Header : DepOpts.ExtraDeps)
232 PrintHeaderInfo(OutputFile, Header.first, ShowDepth, 2, MSStyle);
233 PP.addPPCallbacks(std::make_unique<HeaderIncludesCallback>(
234 &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth,
235 MSStyle));
236 break;
237 }
238 case HIFMT_JSON:
239 switch (DepOpts.HeaderIncludeFiltering) {
240 default:
241 llvm_unreachable("Unknown HeaderIncludeFilteringKind enum");
243 PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>(
244 &PP, OutputFile, OwnsOutputFile));
245 break;
247 PP.addPPCallbacks(std::make_unique<HeaderIncludesDirectPerFileCallback>(
248 &PP, OutputFile, OwnsOutputFile));
249 break;
250 }
251 break;
252 }
253}
254
255void HeaderIncludesCallback::FileChanged(SourceLocation Loc,
256 FileChangeReason Reason,
257 SrcMgr::CharacteristicKind NewFileType,
258 FileID PrevFID) {
259 // Unless we are exiting a #include, make sure to skip ahead to the line the
260 // #include directive was at.
261 PresumedLoc UserLoc = SM.getPresumedLoc(Loc);
262 if (UserLoc.isInvalid())
263 return;
264
265 // Adjust the current include depth.
266 if (Reason == PPCallbacks::EnterFile) {
267 ++CurrentIncludeDepth;
268 } else if (Reason == PPCallbacks::ExitFile) {
269 if (CurrentIncludeDepth)
270 --CurrentIncludeDepth;
271
272 // We track when we are done with the predefines by watching for the first
273 // place where we drop back to a nesting depth of 1.
274 if (CurrentIncludeDepth == 1 && !HasProcessedPredefines)
275 HasProcessedPredefines = true;
276
277 return;
278 } else {
279 return;
280 }
281
282 if (!ShouldShowHeader(NewFileType))
283 return;
284
285 unsigned IncludeDepth = CurrentIncludeDepth;
286 if (!HasProcessedPredefines)
287 --IncludeDepth; // Ignore indent from <built-in>.
288
289 // FIXME: Identify headers in a more robust way than comparing their name to
290 // "<command line>" and "<built-in>" in a bunch of places.
291 if (Reason == PPCallbacks::EnterFile &&
292 UserLoc.getFilename() != StringRef("<command line>")) {
293 PrintHeaderInfo(OutputFile, UserLoc.getFilename(), ShowDepth, IncludeDepth,
294 MSStyle);
295 }
296}
297
298void HeaderIncludesCallback::FileSkipped(const FileEntryRef &SkippedFile, const
299 Token &FilenameTok,
301 if (!DepOpts.ShowSkippedHeaderIncludes)
302 return;
303
304 if (!ShouldShowHeader(FileType))
305 return;
306
307 PrintHeaderInfo(OutputFile, SkippedFile.getName(), ShowDepth,
308 CurrentIncludeDepth + 1, MSStyle);
309}
310
311void HeaderIncludesJSONCallback::EndOfMainFile() {
312 OptionalFileEntryRef FE = SM.getFileEntryRefForID(SM.getMainFileID());
313 SmallString<256> MainFile;
314 if (FE) {
315 MainFile += FE->getName();
316 SM.getFileManager().makeAbsolutePath(MainFile);
317 }
318
319 std::string Str;
320 llvm::raw_string_ostream OS(Str);
321 llvm::json::OStream JOS(OS);
322 JOS.object([&] {
323 JOS.attribute("source", MainFile.c_str());
324 JOS.attributeArray("includes", [&] {
325 llvm::StringSet<> SeenHeaders;
326 for (const std::string &H : IncludedHeaders)
327 if (SeenHeaders.insert(H).second)
328 JOS.value(H);
329 });
330 });
331 OS << "\n";
332
333 if (OutputFile->get_kind() == raw_ostream::OStreamKind::OK_FDStream) {
334 llvm::raw_fd_ostream *FDS = static_cast<llvm::raw_fd_ostream *>(OutputFile);
335 if (auto L = FDS->lock())
336 *OutputFile << Str;
337 } else
338 *OutputFile << Str;
339}
340
341/// Determine whether the header file should be recorded. The header file should
342/// be recorded only if the header file is a system header and the current file
343/// isn't a system header.
345 SourceLocation PrevLoc, SourceManager &SM) {
346 return SrcMgr::isSystem(NewFileType) && !SM.isInSystemHeader(PrevLoc);
347}
348
349void HeaderIncludesJSONCallback::FileChanged(
350 SourceLocation Loc, FileChangeReason Reason,
351 SrcMgr::CharacteristicKind NewFileType, FileID PrevFID) {
352 if (PrevFID.isInvalid() ||
353 !shouldRecordNewFile(NewFileType, SM.getLocForStartOfFile(PrevFID), SM))
354 return;
355
356 // Unless we are exiting a #include, make sure to skip ahead to the line the
357 // #include directive was at.
358 PresumedLoc UserLoc = SM.getPresumedLoc(Loc);
359 if (UserLoc.isInvalid())
360 return;
361
362 if (Reason == PPCallbacks::EnterFile &&
363 UserLoc.getFilename() != StringRef("<command line>"))
364 IncludedHeaders.push_back(UserLoc.getFilename());
365}
366
367void HeaderIncludesJSONCallback::FileSkipped(
368 const FileEntryRef &SkippedFile, const Token &FilenameTok,
370 if (!shouldRecordNewFile(FileType, FilenameTok.getLocation(), SM))
371 return;
372
373 IncludedHeaders.push_back(SkippedFile.getName().str());
374}
375
376void 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
415void 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
433void HeaderIncludesDirectPerFileCallback::moduleImport(SourceLocation ImportLoc,
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}
IndirectLocalPath & Path
StringRef Filename
Definition: Format.cpp:3177
llvm::json::OStream & JOS
Definition: HTMLLogger.cpp:146
static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename, bool ShowDepth, unsigned CurrentIncludeDepth, bool MSStyle)
static bool shouldRecordNewFile(SrcMgr::CharacteristicKind NewFileType, SourceLocation PrevLoc, SourceManager &SM)
Determine whether the header file should be recorded.
llvm::MachO::FileType FileType
Definition: MachO.h:46
#define SM(sm)
Definition: OffloadArch.cpp:16
Defines the clang::Preprocessor interface.
SourceLocation Loc
Definition: SemaObjC.cpp:754
Defines the SourceManager interface.
Represents a character-granular source range.
DependencyOutputOptions - Options for controlling the compiler dependency file generation.
ShowIncludesDestination ShowIncludesDest
Destination of cl.exe style /showIncludes info.
HeaderIncludeFormatKind HeaderIncludeFormat
The format of header information.
HeaderIncludeFilteringKind HeaderIncludeFiltering
Determine whether header information should be filtered.
unsigned ShowSkippedHeaderIncludes
With ShowHeaderIncludes, show also includes that were skipped due to the "include guard optimizatio...
std::vector< std::pair< std::string, ExtraDepKind > > ExtraDeps
A list of extra dependencies (filename and kind) to be used for every target.
unsigned IncludeSystemHeaders
Include system header dependencies.
DiagnosticBuilder Report(SourceLocation Loc, unsigned DiagID)
Issue the message to the client.
Definition: Diagnostic.h:1529
A reference to a FileEntry that includes the name of the file as it was accessed by the FileManager's...
Definition: FileEntry.h:57
StringRef getName() const
The name of this FileEntry.
Definition: FileEntry.h:61
unsigned getUID() const
Definition: FileEntry.h:352
An opaque identifier used by SourceManager which refers to a source file (MemoryBuffer) along with it...
bool isInvalid() const
Encapsulates the information needed to find the file referenced by a #include or #include_next,...
Definition: HeaderSearch.h:237
Record the location of an inclusion directive, such as an #include or #import statement.
static std::string Stringify(StringRef Str, bool Charify=false)
Stringify - Convert the specified string into a C string by i) escaping '\' and " characters and ii) ...
Definition: Lexer.cpp:309
Describes a module or submodule.
Definition: Module.h:144
This interface provides a way to observe the actions of the preprocessor as it does its thing.
Definition: PPCallbacks.h:37
virtual void EndOfMainFile()
Callback invoked when the end of the main file is reached.
Definition: PPCallbacks.h:218
virtual void FileChanged(SourceLocation Loc, FileChangeReason Reason, SrcMgr::CharacteristicKind FileType, FileID PrevFID=FileID())
Callback invoked whenever a source file is entered or exited.
Definition: PPCallbacks.h:50
virtual void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok, SrcMgr::CharacteristicKind FileType)
Callback invoked whenever a source file is skipped as the result of header guard optimization.
Definition: PPCallbacks.h:84
virtual void moduleImport(SourceLocation ImportLoc, ModuleIdPath Path, const Module *Imported)
Callback invoked whenever there was an explicit module-import syntax.
Definition: PPCallbacks.h:210
Engages in a tight little dance with the lexer to efficiently preprocess tokens.
Definition: Preprocessor.h:145
void addPPCallbacks(std::unique_ptr< PPCallbacks > C)
DiagnosticsEngine & getDiagnostics() const
Represents an unpacked "presumed" location which can be presented to the user.
const char * getFilename() const
Return the presumed filename of this location.
bool isInvalid() const
Return true if this object is invalid or uninitialized.
Encodes a location in the source.
This class handles loading and caching of source files into memory.
Token - This structure provides full information about a lexed token.
Definition: Token.h:36
SourceLocation getLocation() const
Return a source location identifier for the specified offset in the current file.
Definition: Token.h:134
CharacteristicKind
Indicates whether a file or directory holds normal user code, system code, or system code which is im...
Definition: SourceManager.h:81
bool isSystem(CharacteristicKind CK)
Determine whether a file / directory characteristic is for system code.
Definition: SourceManager.h:90
The JSON file list parser is used to communicate input to InstallAPI.
@ HIFIL_None
Definition: HeaderInclude.h:30
@ HIFIL_Direct_Per_File
Definition: HeaderInclude.h:32
@ HIFIL_Only_Direct_System
Definition: HeaderInclude.h:31
void AttachHeaderIncludeGen(Preprocessor &PP, const DependencyOutputOptions &DepOpts, bool ShowAllHeaders=false, StringRef OutputPath={}, bool ShowDepth=true, bool MSStyle=false)
AttachHeaderIncludeGen - Create a header include list generator, and attach it to the given preproces...
@ HIFMT_None
Definition: HeaderInclude.h:22
@ HIFMT_JSON
Definition: HeaderInclude.h:22
@ HIFMT_Textual
Definition: HeaderInclude.h:22
#define false
Definition: stdbool.h:26