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 : public PPCallbacks { |
21 | SourceManager &; |
22 | raw_ostream *; |
23 | const DependencyOutputOptions &; |
24 | unsigned ; |
25 | bool ; |
26 | bool ; |
27 | bool ; |
28 | bool ; |
29 | bool ; |
30 | |
31 | public: |
32 | (const Preprocessor *PP, bool , |
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 | () override { |
42 | if (OwnsOutputFile) |
43 | delete OutputFile; |
44 | } |
45 | |
46 | (const HeaderIncludesCallback &) = delete; |
47 | HeaderIncludesCallback &(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 (SrcMgr::CharacteristicKind ) { |
58 | if (!DepOpts.IncludeSystemHeaders && isSystem(CK: 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 : public PPCallbacks { |
80 | SourceManager &; |
81 | raw_ostream *; |
82 | bool ; |
83 | SmallVector<std::string, 16> ; |
84 | |
85 | public: |
86 | (const Preprocessor *PP, raw_ostream *OutputFile_, |
87 | bool OwnsOutputFile_) |
88 | : SM(PP->getSourceManager()), OutputFile(OutputFile_), |
89 | OwnsOutputFile(OwnsOutputFile_) {} |
90 | |
91 | () override { |
92 | if (OwnsOutputFile) |
93 | delete OutputFile; |
94 | } |
95 | |
96 | (const HeaderIncludesJSONCallback &) = delete; |
97 | HeaderIncludesJSONCallback & |
98 | (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 (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(Str&: 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::(Preprocessor &PP, |
139 | const DependencyOutputOptions &DepOpts, |
140 | bool , 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 & : DepOpts.ExtraDeps) |
189 | PrintHeaderInfo(OutputFile, Filename: Header.first, ShowDepth, CurrentIncludeDepth: 2, MSStyle); |
190 | PP.addPPCallbacks(C: std::make_unique<HeaderIncludesCallback>( |
191 | args: &PP, args&: ShowAllHeaders, args&: OutputFile, args: DepOpts, args&: OwnsOutputFile, args&: ShowDepth, |
192 | args&: 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(C: std::make_unique<HeaderIncludesJSONCallback>( |
199 | args: &PP, args&: OutputFile, args&: OwnsOutputFile)); |
200 | break; |
201 | } |
202 | } |
203 | } |
204 | |
205 | void HeaderIncludesCallback::(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(HeaderType: 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, Filename: UserLoc.getFilename(), ShowDepth, CurrentIncludeDepth: IncludeDepth, |
244 | MSStyle); |
245 | } |
246 | } |
247 | |
248 | void HeaderIncludesCallback::(const FileEntryRef &SkippedFile, const |
249 | Token &FilenameTok, |
250 | SrcMgr::CharacteristicKind FileType) { |
251 | if (!DepOpts.ShowSkippedHeaderIncludes) |
252 | return; |
253 | |
254 | if (!ShouldShowHeader(HeaderType: FileType)) |
255 | return; |
256 | |
257 | PrintHeaderInfo(OutputFile, Filename: SkippedFile.getName(), ShowDepth, |
258 | CurrentIncludeDepth: CurrentIncludeDepth + 1, MSStyle); |
259 | } |
260 | |
261 | void HeaderIncludesJSONCallback::EndOfMainFile() { |
262 | OptionalFileEntryRef FE = SM.getFileEntryRefForID(FID: SM.getMainFileID()); |
263 | SmallString<256> MainFile(FE->getName()); |
264 | SM.getFileManager().makeAbsolutePath(Path&: MainFile); |
265 | |
266 | std::string Str; |
267 | llvm::raw_string_ostream OS(Str); |
268 | llvm::json::OStream JOS(OS); |
269 | JOS.object(Contents: [&] { |
270 | JOS.attribute(Key: "source" , Contents: MainFile.c_str()); |
271 | JOS.attributeArray(Key: "includes" , Contents: [&] { |
272 | llvm::StringSet<> ; |
273 | for (const std::string &H : IncludedHeaders) |
274 | if (SeenHeaders.insert(key: H).second) |
275 | JOS.value(V: 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(CK: NewFileType) && !SM.isInSystemHeader(Loc: PrevLoc); |
294 | } |
295 | |
296 | void HeaderIncludesJSONCallback::( |
297 | SourceLocation Loc, FileChangeReason Reason, |
298 | SrcMgr::CharacteristicKind NewFileType, FileID PrevFID) { |
299 | if (PrevFID.isInvalid() || |
300 | !shouldRecordNewFile(NewFileType, PrevLoc: SM.getLocForStartOfFile(FID: 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(Elt: UserLoc.getFilename()); |
312 | } |
313 | |
314 | void HeaderIncludesJSONCallback::( |
315 | const FileEntryRef &SkippedFile, const Token &FilenameTok, |
316 | SrcMgr::CharacteristicKind FileType) { |
317 | if (!shouldRecordNewFile(NewFileType: FileType, PrevLoc: FilenameTok.getLocation(), SM)) |
318 | return; |
319 | |
320 | IncludedHeaders.push_back(Elt: SkippedFile.getName().str()); |
321 | } |
322 | |