1e5dd7070Spatrick //===-- HeaderIncludeGen.cpp - Generate Header Includes -------------------===//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick
9e5dd7070Spatrick #include "clang/Frontend/DependencyOutputOptions.h"
10e5dd7070Spatrick #include "clang/Frontend/Utils.h"
11e5dd7070Spatrick #include "clang/Basic/SourceManager.h"
12e5dd7070Spatrick #include "clang/Frontend/FrontendDiagnostic.h"
13e5dd7070Spatrick #include "clang/Lex/Preprocessor.h"
14e5dd7070Spatrick #include "llvm/ADT/SmallString.h"
15*12c85518Srobert #include "llvm/Support/JSON.h"
16e5dd7070Spatrick #include "llvm/Support/raw_ostream.h"
17e5dd7070Spatrick using namespace clang;
18e5dd7070Spatrick
19e5dd7070Spatrick namespace {
20e5dd7070Spatrick class HeaderIncludesCallback : public PPCallbacks {
21e5dd7070Spatrick SourceManager &SM;
22e5dd7070Spatrick raw_ostream *OutputFile;
23e5dd7070Spatrick const DependencyOutputOptions &DepOpts;
24e5dd7070Spatrick unsigned CurrentIncludeDepth;
25e5dd7070Spatrick bool HasProcessedPredefines;
26e5dd7070Spatrick bool OwnsOutputFile;
27e5dd7070Spatrick bool ShowAllHeaders;
28e5dd7070Spatrick bool ShowDepth;
29e5dd7070Spatrick bool MSStyle;
30e5dd7070Spatrick
31e5dd7070Spatrick public:
HeaderIncludesCallback(const Preprocessor * PP,bool ShowAllHeaders_,raw_ostream * OutputFile_,const DependencyOutputOptions & DepOpts,bool OwnsOutputFile_,bool ShowDepth_,bool MSStyle_)32e5dd7070Spatrick HeaderIncludesCallback(const Preprocessor *PP, bool ShowAllHeaders_,
33e5dd7070Spatrick raw_ostream *OutputFile_,
34e5dd7070Spatrick const DependencyOutputOptions &DepOpts,
35e5dd7070Spatrick bool OwnsOutputFile_, bool ShowDepth_, bool MSStyle_)
36e5dd7070Spatrick : SM(PP->getSourceManager()), OutputFile(OutputFile_), DepOpts(DepOpts),
37e5dd7070Spatrick CurrentIncludeDepth(0), HasProcessedPredefines(false),
38e5dd7070Spatrick OwnsOutputFile(OwnsOutputFile_), ShowAllHeaders(ShowAllHeaders_),
39e5dd7070Spatrick ShowDepth(ShowDepth_), MSStyle(MSStyle_) {}
40e5dd7070Spatrick
~HeaderIncludesCallback()41e5dd7070Spatrick ~HeaderIncludesCallback() override {
42e5dd7070Spatrick if (OwnsOutputFile)
43e5dd7070Spatrick delete OutputFile;
44e5dd7070Spatrick }
45e5dd7070Spatrick
46e5dd7070Spatrick void FileChanged(SourceLocation Loc, FileChangeReason Reason,
47e5dd7070Spatrick SrcMgr::CharacteristicKind FileType,
48e5dd7070Spatrick FileID PrevFID) override;
49a9ac8606Spatrick
50a9ac8606Spatrick void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
51a9ac8606Spatrick SrcMgr::CharacteristicKind FileType) override;
52e5dd7070Spatrick };
53*12c85518Srobert
54*12c85518Srobert /// A callback for emitting header usage information to a file in JSON. Each
55*12c85518Srobert /// line in the file is a JSON object that includes the source file name and
56*12c85518Srobert /// the list of headers directly or indirectly included from it. For example:
57*12c85518Srobert ///
58*12c85518Srobert /// {"source":"/tmp/foo.c",
59*12c85518Srobert /// "includes":["/usr/include/stdio.h", "/usr/include/stdlib.h"]}
60*12c85518Srobert ///
61*12c85518Srobert /// To reduce the amount of data written to the file, we only record system
62*12c85518Srobert /// headers that are directly included from a file that isn't in the system
63*12c85518Srobert /// directory.
64*12c85518Srobert class HeaderIncludesJSONCallback : public PPCallbacks {
65*12c85518Srobert SourceManager &SM;
66*12c85518Srobert raw_ostream *OutputFile;
67*12c85518Srobert bool OwnsOutputFile;
68*12c85518Srobert SmallVector<std::string, 16> IncludedHeaders;
69*12c85518Srobert
70*12c85518Srobert public:
HeaderIncludesJSONCallback(const Preprocessor * PP,raw_ostream * OutputFile_,bool OwnsOutputFile_)71*12c85518Srobert HeaderIncludesJSONCallback(const Preprocessor *PP, raw_ostream *OutputFile_,
72*12c85518Srobert bool OwnsOutputFile_)
73*12c85518Srobert : SM(PP->getSourceManager()), OutputFile(OutputFile_),
74*12c85518Srobert OwnsOutputFile(OwnsOutputFile_) {}
75*12c85518Srobert
~HeaderIncludesJSONCallback()76*12c85518Srobert ~HeaderIncludesJSONCallback() override {
77*12c85518Srobert if (OwnsOutputFile)
78*12c85518Srobert delete OutputFile;
79*12c85518Srobert }
80*12c85518Srobert
81*12c85518Srobert void EndOfMainFile() override;
82*12c85518Srobert
83*12c85518Srobert void FileChanged(SourceLocation Loc, FileChangeReason Reason,
84*12c85518Srobert SrcMgr::CharacteristicKind FileType,
85*12c85518Srobert FileID PrevFID) override;
86*12c85518Srobert
87*12c85518Srobert void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
88*12c85518Srobert SrcMgr::CharacteristicKind FileType) override;
89*12c85518Srobert };
90e5dd7070Spatrick }
91e5dd7070Spatrick
PrintHeaderInfo(raw_ostream * OutputFile,StringRef Filename,bool ShowDepth,unsigned CurrentIncludeDepth,bool MSStyle)92e5dd7070Spatrick static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename,
93e5dd7070Spatrick bool ShowDepth, unsigned CurrentIncludeDepth,
94e5dd7070Spatrick bool MSStyle) {
95e5dd7070Spatrick // Write to a temporary string to avoid unnecessary flushing on errs().
96e5dd7070Spatrick SmallString<512> Pathname(Filename);
97e5dd7070Spatrick if (!MSStyle)
98e5dd7070Spatrick Lexer::Stringify(Pathname);
99e5dd7070Spatrick
100e5dd7070Spatrick SmallString<256> Msg;
101e5dd7070Spatrick if (MSStyle)
102e5dd7070Spatrick Msg += "Note: including file:";
103e5dd7070Spatrick
104e5dd7070Spatrick if (ShowDepth) {
105e5dd7070Spatrick // The main source file is at depth 1, so skip one dot.
106e5dd7070Spatrick for (unsigned i = 1; i != CurrentIncludeDepth; ++i)
107e5dd7070Spatrick Msg += MSStyle ? ' ' : '.';
108e5dd7070Spatrick
109e5dd7070Spatrick if (!MSStyle)
110e5dd7070Spatrick Msg += ' ';
111e5dd7070Spatrick }
112e5dd7070Spatrick Msg += Pathname;
113e5dd7070Spatrick Msg += '\n';
114e5dd7070Spatrick
115e5dd7070Spatrick *OutputFile << Msg;
116e5dd7070Spatrick OutputFile->flush();
117e5dd7070Spatrick }
118e5dd7070Spatrick
AttachHeaderIncludeGen(Preprocessor & PP,const DependencyOutputOptions & DepOpts,bool ShowAllHeaders,StringRef OutputPath,bool ShowDepth,bool MSStyle)119e5dd7070Spatrick void clang::AttachHeaderIncludeGen(Preprocessor &PP,
120e5dd7070Spatrick const DependencyOutputOptions &DepOpts,
121e5dd7070Spatrick bool ShowAllHeaders, StringRef OutputPath,
122e5dd7070Spatrick bool ShowDepth, bool MSStyle) {
123e5dd7070Spatrick raw_ostream *OutputFile = &llvm::errs();
124e5dd7070Spatrick bool OwnsOutputFile = false;
125e5dd7070Spatrick
126e5dd7070Spatrick // Choose output stream, when printing in cl.exe /showIncludes style.
127e5dd7070Spatrick if (MSStyle) {
128e5dd7070Spatrick switch (DepOpts.ShowIncludesDest) {
129e5dd7070Spatrick default:
130e5dd7070Spatrick llvm_unreachable("Invalid destination for /showIncludes output!");
131e5dd7070Spatrick case ShowIncludesDestination::Stderr:
132e5dd7070Spatrick OutputFile = &llvm::errs();
133e5dd7070Spatrick break;
134e5dd7070Spatrick case ShowIncludesDestination::Stdout:
135e5dd7070Spatrick OutputFile = &llvm::outs();
136e5dd7070Spatrick break;
137e5dd7070Spatrick }
138e5dd7070Spatrick }
139e5dd7070Spatrick
140e5dd7070Spatrick // Open the output file, if used.
141e5dd7070Spatrick if (!OutputPath.empty()) {
142e5dd7070Spatrick std::error_code EC;
143e5dd7070Spatrick llvm::raw_fd_ostream *OS = new llvm::raw_fd_ostream(
144e5dd7070Spatrick OutputPath.str(), EC,
145a9ac8606Spatrick llvm::sys::fs::OF_Append | llvm::sys::fs::OF_TextWithCRLF);
146e5dd7070Spatrick if (EC) {
147e5dd7070Spatrick PP.getDiagnostics().Report(clang::diag::warn_fe_cc_print_header_failure)
148e5dd7070Spatrick << EC.message();
149e5dd7070Spatrick delete OS;
150e5dd7070Spatrick } else {
151e5dd7070Spatrick OS->SetUnbuffered();
152e5dd7070Spatrick OutputFile = OS;
153e5dd7070Spatrick OwnsOutputFile = true;
154e5dd7070Spatrick }
155e5dd7070Spatrick }
156e5dd7070Spatrick
157*12c85518Srobert switch (DepOpts.HeaderIncludeFormat) {
158*12c85518Srobert case HIFMT_None:
159*12c85518Srobert llvm_unreachable("unexpected header format kind");
160*12c85518Srobert case HIFMT_Textual: {
161*12c85518Srobert assert(DepOpts.HeaderIncludeFiltering == HIFIL_None &&
162*12c85518Srobert "header filtering is currently always disabled when output format is"
163*12c85518Srobert "textual");
164e5dd7070Spatrick // Print header info for extra headers, pretending they were discovered by
165e5dd7070Spatrick // the regular preprocessor. The primary use case is to support proper
166e5dd7070Spatrick // generation of Make / Ninja file dependencies for implicit includes, such
167*12c85518Srobert // as sanitizer ignorelists. It's only important for cl.exe compatibility,
168e5dd7070Spatrick // the GNU way to generate rules is -M / -MM / -MD / -MMD.
169e5dd7070Spatrick for (const auto &Header : DepOpts.ExtraDeps)
170a9ac8606Spatrick PrintHeaderInfo(OutputFile, Header.first, ShowDepth, 2, MSStyle);
171e5dd7070Spatrick PP.addPPCallbacks(std::make_unique<HeaderIncludesCallback>(
172e5dd7070Spatrick &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth,
173e5dd7070Spatrick MSStyle));
174*12c85518Srobert break;
175*12c85518Srobert }
176*12c85518Srobert case HIFMT_JSON: {
177*12c85518Srobert assert(DepOpts.HeaderIncludeFiltering == HIFIL_Only_Direct_System &&
178*12c85518Srobert "only-direct-system is the only option for filtering");
179*12c85518Srobert PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>(
180*12c85518Srobert &PP, OutputFile, OwnsOutputFile));
181*12c85518Srobert break;
182*12c85518Srobert }
183*12c85518Srobert }
184e5dd7070Spatrick }
185e5dd7070Spatrick
FileChanged(SourceLocation Loc,FileChangeReason Reason,SrcMgr::CharacteristicKind NewFileType,FileID PrevFID)186e5dd7070Spatrick void HeaderIncludesCallback::FileChanged(SourceLocation Loc,
187e5dd7070Spatrick FileChangeReason Reason,
188e5dd7070Spatrick SrcMgr::CharacteristicKind NewFileType,
189e5dd7070Spatrick FileID PrevFID) {
190e5dd7070Spatrick // Unless we are exiting a #include, make sure to skip ahead to the line the
191e5dd7070Spatrick // #include directive was at.
192e5dd7070Spatrick PresumedLoc UserLoc = SM.getPresumedLoc(Loc);
193e5dd7070Spatrick if (UserLoc.isInvalid())
194e5dd7070Spatrick return;
195e5dd7070Spatrick
196e5dd7070Spatrick // Adjust the current include depth.
197e5dd7070Spatrick if (Reason == PPCallbacks::EnterFile) {
198e5dd7070Spatrick ++CurrentIncludeDepth;
199e5dd7070Spatrick } else if (Reason == PPCallbacks::ExitFile) {
200e5dd7070Spatrick if (CurrentIncludeDepth)
201e5dd7070Spatrick --CurrentIncludeDepth;
202e5dd7070Spatrick
203e5dd7070Spatrick // We track when we are done with the predefines by watching for the first
204e5dd7070Spatrick // place where we drop back to a nesting depth of 1.
205e5dd7070Spatrick if (CurrentIncludeDepth == 1 && !HasProcessedPredefines) {
206e5dd7070Spatrick if (!DepOpts.ShowIncludesPretendHeader.empty()) {
207e5dd7070Spatrick PrintHeaderInfo(OutputFile, DepOpts.ShowIncludesPretendHeader,
208e5dd7070Spatrick ShowDepth, 2, MSStyle);
209e5dd7070Spatrick }
210e5dd7070Spatrick HasProcessedPredefines = true;
211e5dd7070Spatrick }
212e5dd7070Spatrick
213e5dd7070Spatrick return;
214e5dd7070Spatrick } else
215e5dd7070Spatrick return;
216e5dd7070Spatrick
217e5dd7070Spatrick // Show the header if we are (a) past the predefines, or (b) showing all
218e5dd7070Spatrick // headers and in the predefines at a depth past the initial file and command
219e5dd7070Spatrick // line buffers.
220e5dd7070Spatrick bool ShowHeader = (HasProcessedPredefines ||
221e5dd7070Spatrick (ShowAllHeaders && CurrentIncludeDepth > 2));
222e5dd7070Spatrick unsigned IncludeDepth = CurrentIncludeDepth;
223e5dd7070Spatrick if (!HasProcessedPredefines)
224e5dd7070Spatrick --IncludeDepth; // Ignore indent from <built-in>.
225e5dd7070Spatrick else if (!DepOpts.ShowIncludesPretendHeader.empty())
226e5dd7070Spatrick ++IncludeDepth; // Pretend inclusion by ShowIncludesPretendHeader.
227ec727ea7Spatrick
228ec727ea7Spatrick if (!DepOpts.IncludeSystemHeaders && isSystem(NewFileType))
229ec727ea7Spatrick ShowHeader = false;
230e5dd7070Spatrick
231e5dd7070Spatrick // Dump the header include information we are past the predefines buffer or
232e5dd7070Spatrick // are showing all headers and this isn't the magic implicit <command line>
233e5dd7070Spatrick // header.
234e5dd7070Spatrick // FIXME: Identify headers in a more robust way than comparing their name to
235e5dd7070Spatrick // "<command line>" and "<built-in>" in a bunch of places.
236e5dd7070Spatrick if (ShowHeader && Reason == PPCallbacks::EnterFile &&
237e5dd7070Spatrick UserLoc.getFilename() != StringRef("<command line>")) {
238e5dd7070Spatrick PrintHeaderInfo(OutputFile, UserLoc.getFilename(), ShowDepth, IncludeDepth,
239e5dd7070Spatrick MSStyle);
240e5dd7070Spatrick }
241e5dd7070Spatrick }
242a9ac8606Spatrick
FileSkipped(const FileEntryRef & SkippedFile,const Token & FilenameTok,SrcMgr::CharacteristicKind FileType)243a9ac8606Spatrick void HeaderIncludesCallback::FileSkipped(const FileEntryRef &SkippedFile, const
244a9ac8606Spatrick Token &FilenameTok,
245a9ac8606Spatrick SrcMgr::CharacteristicKind FileType) {
246a9ac8606Spatrick if (!DepOpts.ShowSkippedHeaderIncludes)
247a9ac8606Spatrick return;
248a9ac8606Spatrick
249a9ac8606Spatrick if (!DepOpts.IncludeSystemHeaders && isSystem(FileType))
250a9ac8606Spatrick return;
251a9ac8606Spatrick
252a9ac8606Spatrick PrintHeaderInfo(OutputFile, SkippedFile.getName(), ShowDepth,
253a9ac8606Spatrick CurrentIncludeDepth + 1, MSStyle);
254a9ac8606Spatrick }
255*12c85518Srobert
EndOfMainFile()256*12c85518Srobert void HeaderIncludesJSONCallback::EndOfMainFile() {
257*12c85518Srobert const FileEntry *FE = SM.getFileEntryForID(SM.getMainFileID());
258*12c85518Srobert SmallString<256> MainFile(FE->getName());
259*12c85518Srobert SM.getFileManager().makeAbsolutePath(MainFile);
260*12c85518Srobert
261*12c85518Srobert std::string Str;
262*12c85518Srobert llvm::raw_string_ostream OS(Str);
263*12c85518Srobert llvm::json::OStream JOS(OS);
264*12c85518Srobert JOS.object([&] {
265*12c85518Srobert JOS.attribute("source", MainFile.c_str());
266*12c85518Srobert JOS.attributeArray("includes", [&] {
267*12c85518Srobert llvm::StringSet<> SeenHeaders;
268*12c85518Srobert for (const std::string &H : IncludedHeaders)
269*12c85518Srobert if (SeenHeaders.insert(H).second)
270*12c85518Srobert JOS.value(H);
271*12c85518Srobert });
272*12c85518Srobert });
273*12c85518Srobert OS << "\n";
274*12c85518Srobert
275*12c85518Srobert if (OutputFile->get_kind() == raw_ostream::OStreamKind::OK_FDStream) {
276*12c85518Srobert llvm::raw_fd_ostream *FDS = static_cast<llvm::raw_fd_ostream *>(OutputFile);
277*12c85518Srobert if (auto L = FDS->lock())
278*12c85518Srobert *OutputFile << Str;
279*12c85518Srobert } else
280*12c85518Srobert *OutputFile << Str;
281*12c85518Srobert }
282*12c85518Srobert
283*12c85518Srobert /// Determine whether the header file should be recorded. The header file should
284*12c85518Srobert /// be recorded only if the header file is a system header and the current file
285*12c85518Srobert /// isn't a system header.
shouldRecordNewFile(SrcMgr::CharacteristicKind NewFileType,SourceLocation PrevLoc,SourceManager & SM)286*12c85518Srobert static bool shouldRecordNewFile(SrcMgr::CharacteristicKind NewFileType,
287*12c85518Srobert SourceLocation PrevLoc, SourceManager &SM) {
288*12c85518Srobert return SrcMgr::isSystem(NewFileType) && !SM.isInSystemHeader(PrevLoc);
289*12c85518Srobert }
290*12c85518Srobert
FileChanged(SourceLocation Loc,FileChangeReason Reason,SrcMgr::CharacteristicKind NewFileType,FileID PrevFID)291*12c85518Srobert void HeaderIncludesJSONCallback::FileChanged(
292*12c85518Srobert SourceLocation Loc, FileChangeReason Reason,
293*12c85518Srobert SrcMgr::CharacteristicKind NewFileType, FileID PrevFID) {
294*12c85518Srobert if (PrevFID.isInvalid() ||
295*12c85518Srobert !shouldRecordNewFile(NewFileType, SM.getLocForStartOfFile(PrevFID), SM))
296*12c85518Srobert return;
297*12c85518Srobert
298*12c85518Srobert // Unless we are exiting a #include, make sure to skip ahead to the line the
299*12c85518Srobert // #include directive was at.
300*12c85518Srobert PresumedLoc UserLoc = SM.getPresumedLoc(Loc);
301*12c85518Srobert if (UserLoc.isInvalid())
302*12c85518Srobert return;
303*12c85518Srobert
304*12c85518Srobert if (Reason == PPCallbacks::EnterFile &&
305*12c85518Srobert UserLoc.getFilename() != StringRef("<command line>"))
306*12c85518Srobert IncludedHeaders.push_back(UserLoc.getFilename());
307*12c85518Srobert }
308*12c85518Srobert
FileSkipped(const FileEntryRef & SkippedFile,const Token & FilenameTok,SrcMgr::CharacteristicKind FileType)309*12c85518Srobert void HeaderIncludesJSONCallback::FileSkipped(
310*12c85518Srobert const FileEntryRef &SkippedFile, const Token &FilenameTok,
311*12c85518Srobert SrcMgr::CharacteristicKind FileType) {
312*12c85518Srobert if (!shouldRecordNewFile(FileType, FilenameTok.getLocation(), SM))
313*12c85518Srobert return;
314*12c85518Srobert
315*12c85518Srobert IncludedHeaders.push_back(SkippedFile.getName().str());
316*12c85518Srobert }
317