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