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