xref: /openbsd-src/gnu/llvm/clang/lib/Frontend/HeaderIncludeGen.cpp (revision 12c855180aad702bbcca06e0398d774beeafb155)
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