/src/llvm-project/clang/lib/Frontend/HeaderIncludeGen.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 0 | ShowDepth(ShowDepth_), MSStyle(MSStyle_) {} |
40 | | |
41 | 0 | ~HeaderIncludesCallback() override { |
42 | 0 | if (OwnsOutputFile) |
43 | 0 | delete OutputFile; |
44 | 0 | } |
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 | 0 | bool ShouldShowHeader(SrcMgr::CharacteristicKind HeaderType) { |
58 | 0 | if (!DepOpts.IncludeSystemHeaders && isSystem(HeaderType)) |
59 | 0 | 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 | 0 | return (HasProcessedPredefines || |
65 | 0 | (ShowAllHeaders && CurrentIncludeDepth > 2)); |
66 | 0 | } |
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 | 0 | OwnsOutputFile(OwnsOutputFile_) {} |
90 | | |
91 | 0 | ~HeaderIncludesJSONCallback() override { |
92 | 0 | if (OwnsOutputFile) |
93 | 0 | delete OutputFile; |
94 | 0 | } |
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 | 0 | bool MSStyle) { |
114 | | // Write to a temporary string to avoid unnecessary flushing on errs(). |
115 | 0 | SmallString<512> Pathname(Filename); |
116 | 0 | if (!MSStyle) |
117 | 0 | Lexer::Stringify(Pathname); |
118 | |
|
119 | 0 | SmallString<256> Msg; |
120 | 0 | if (MSStyle) |
121 | 0 | Msg += "Note: including file:"; |
122 | |
|
123 | 0 | if (ShowDepth) { |
124 | | // The main source file is at depth 1, so skip one dot. |
125 | 0 | for (unsigned i = 1; i != CurrentIncludeDepth; ++i) |
126 | 0 | Msg += MSStyle ? ' ' : '.'; |
127 | |
|
128 | 0 | if (!MSStyle) |
129 | 0 | Msg += ' '; |
130 | 0 | } |
131 | 0 | Msg += Pathname; |
132 | 0 | Msg += '\n'; |
133 | |
|
134 | 0 | *OutputFile << Msg; |
135 | 0 | OutputFile->flush(); |
136 | 0 | } |
137 | | |
138 | | void clang::AttachHeaderIncludeGen(Preprocessor &PP, |
139 | | const DependencyOutputOptions &DepOpts, |
140 | | bool ShowAllHeaders, StringRef OutputPath, |
141 | 0 | bool ShowDepth, bool MSStyle) { |
142 | 0 | raw_ostream *OutputFile = &llvm::errs(); |
143 | 0 | bool OwnsOutputFile = false; |
144 | | |
145 | | // Choose output stream, when printing in cl.exe /showIncludes style. |
146 | 0 | if (MSStyle) { |
147 | 0 | switch (DepOpts.ShowIncludesDest) { |
148 | 0 | default: |
149 | 0 | llvm_unreachable("Invalid destination for /showIncludes output!"); |
150 | 0 | case ShowIncludesDestination::Stderr: |
151 | 0 | OutputFile = &llvm::errs(); |
152 | 0 | break; |
153 | 0 | case ShowIncludesDestination::Stdout: |
154 | 0 | OutputFile = &llvm::outs(); |
155 | 0 | break; |
156 | 0 | } |
157 | 0 | } |
158 | | |
159 | | // Open the output file, if used. |
160 | 0 | if (!OutputPath.empty()) { |
161 | 0 | std::error_code EC; |
162 | 0 | llvm::raw_fd_ostream *OS = new llvm::raw_fd_ostream( |
163 | 0 | OutputPath.str(), EC, |
164 | 0 | llvm::sys::fs::OF_Append | llvm::sys::fs::OF_TextWithCRLF); |
165 | 0 | if (EC) { |
166 | 0 | PP.getDiagnostics().Report(clang::diag::warn_fe_cc_print_header_failure) |
167 | 0 | << EC.message(); |
168 | 0 | delete OS; |
169 | 0 | } else { |
170 | 0 | OS->SetUnbuffered(); |
171 | 0 | OutputFile = OS; |
172 | 0 | OwnsOutputFile = true; |
173 | 0 | } |
174 | 0 | } |
175 | |
|
176 | 0 | switch (DepOpts.HeaderIncludeFormat) { |
177 | 0 | case HIFMT_None: |
178 | 0 | llvm_unreachable("unexpected header format kind"); |
179 | 0 | case HIFMT_Textual: { |
180 | 0 | assert(DepOpts.HeaderIncludeFiltering == HIFIL_None && |
181 | 0 | "header filtering is currently always disabled when output format is" |
182 | 0 | "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 | 0 | for (const auto &Header : DepOpts.ExtraDeps) |
189 | 0 | PrintHeaderInfo(OutputFile, Header.first, ShowDepth, 2, MSStyle); |
190 | 0 | PP.addPPCallbacks(std::make_unique<HeaderIncludesCallback>( |
191 | 0 | &PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth, |
192 | 0 | MSStyle)); |
193 | 0 | break; |
194 | 0 | } |
195 | 0 | case HIFMT_JSON: { |
196 | 0 | assert(DepOpts.HeaderIncludeFiltering == HIFIL_Only_Direct_System && |
197 | 0 | "only-direct-system is the only option for filtering"); |
198 | 0 | PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>( |
199 | 0 | &PP, OutputFile, OwnsOutputFile)); |
200 | 0 | break; |
201 | 0 | } |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | void HeaderIncludesCallback::FileChanged(SourceLocation Loc, |
206 | | FileChangeReason Reason, |
207 | | SrcMgr::CharacteristicKind NewFileType, |
208 | 0 | FileID PrevFID) { |
209 | | // Unless we are exiting a #include, make sure to skip ahead to the line the |
210 | | // #include directive was at. |
211 | 0 | PresumedLoc UserLoc = SM.getPresumedLoc(Loc); |
212 | 0 | if (UserLoc.isInvalid()) |
213 | 0 | return; |
214 | | |
215 | | // Adjust the current include depth. |
216 | 0 | if (Reason == PPCallbacks::EnterFile) { |
217 | 0 | ++CurrentIncludeDepth; |
218 | 0 | } else if (Reason == PPCallbacks::ExitFile) { |
219 | 0 | if (CurrentIncludeDepth) |
220 | 0 | --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 | 0 | if (CurrentIncludeDepth == 1 && !HasProcessedPredefines) |
225 | 0 | HasProcessedPredefines = true; |
226 | |
|
227 | 0 | return; |
228 | 0 | } else { |
229 | 0 | return; |
230 | 0 | } |
231 | | |
232 | 0 | if (!ShouldShowHeader(NewFileType)) |
233 | 0 | return; |
234 | | |
235 | 0 | unsigned IncludeDepth = CurrentIncludeDepth; |
236 | 0 | if (!HasProcessedPredefines) |
237 | 0 | --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 | 0 | if (Reason == PPCallbacks::EnterFile && |
242 | 0 | UserLoc.getFilename() != StringRef("<command line>")) { |
243 | 0 | PrintHeaderInfo(OutputFile, UserLoc.getFilename(), ShowDepth, IncludeDepth, |
244 | 0 | MSStyle); |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | | void HeaderIncludesCallback::FileSkipped(const FileEntryRef &SkippedFile, const |
249 | | Token &FilenameTok, |
250 | 0 | SrcMgr::CharacteristicKind FileType) { |
251 | 0 | if (!DepOpts.ShowSkippedHeaderIncludes) |
252 | 0 | return; |
253 | | |
254 | 0 | if (!ShouldShowHeader(FileType)) |
255 | 0 | return; |
256 | | |
257 | 0 | PrintHeaderInfo(OutputFile, SkippedFile.getName(), ShowDepth, |
258 | 0 | CurrentIncludeDepth + 1, MSStyle); |
259 | 0 | } |
260 | | |
261 | 0 | void HeaderIncludesJSONCallback::EndOfMainFile() { |
262 | 0 | OptionalFileEntryRef FE = SM.getFileEntryRefForID(SM.getMainFileID()); |
263 | 0 | SmallString<256> MainFile(FE->getName()); |
264 | 0 | SM.getFileManager().makeAbsolutePath(MainFile); |
265 | |
|
266 | 0 | std::string Str; |
267 | 0 | llvm::raw_string_ostream OS(Str); |
268 | 0 | llvm::json::OStream JOS(OS); |
269 | 0 | JOS.object([&] { |
270 | 0 | JOS.attribute("source", MainFile.c_str()); |
271 | 0 | JOS.attributeArray("includes", [&] { |
272 | 0 | llvm::StringSet<> SeenHeaders; |
273 | 0 | for (const std::string &H : IncludedHeaders) |
274 | 0 | if (SeenHeaders.insert(H).second) |
275 | 0 | JOS.value(H); |
276 | 0 | }); |
277 | 0 | }); |
278 | 0 | OS << "\n"; |
279 | |
|
280 | 0 | if (OutputFile->get_kind() == raw_ostream::OStreamKind::OK_FDStream) { |
281 | 0 | llvm::raw_fd_ostream *FDS = static_cast<llvm::raw_fd_ostream *>(OutputFile); |
282 | 0 | if (auto L = FDS->lock()) |
283 | 0 | *OutputFile << Str; |
284 | 0 | } else |
285 | 0 | *OutputFile << Str; |
286 | 0 | } |
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 | 0 | SourceLocation PrevLoc, SourceManager &SM) { |
293 | 0 | return SrcMgr::isSystem(NewFileType) && !SM.isInSystemHeader(PrevLoc); |
294 | 0 | } |
295 | | |
296 | | void HeaderIncludesJSONCallback::FileChanged( |
297 | | SourceLocation Loc, FileChangeReason Reason, |
298 | 0 | SrcMgr::CharacteristicKind NewFileType, FileID PrevFID) { |
299 | 0 | if (PrevFID.isInvalid() || |
300 | 0 | !shouldRecordNewFile(NewFileType, SM.getLocForStartOfFile(PrevFID), SM)) |
301 | 0 | return; |
302 | | |
303 | | // Unless we are exiting a #include, make sure to skip ahead to the line the |
304 | | // #include directive was at. |
305 | 0 | PresumedLoc UserLoc = SM.getPresumedLoc(Loc); |
306 | 0 | if (UserLoc.isInvalid()) |
307 | 0 | return; |
308 | | |
309 | 0 | if (Reason == PPCallbacks::EnterFile && |
310 | 0 | UserLoc.getFilename() != StringRef("<command line>")) |
311 | 0 | IncludedHeaders.push_back(UserLoc.getFilename()); |
312 | 0 | } |
313 | | |
314 | | void HeaderIncludesJSONCallback::FileSkipped( |
315 | | const FileEntryRef &SkippedFile, const Token &FilenameTok, |
316 | 0 | SrcMgr::CharacteristicKind FileType) { |
317 | 0 | if (!shouldRecordNewFile(FileType, FilenameTok.getLocation(), SM)) |
318 | 0 | return; |
319 | | |
320 | 0 | IncludedHeaders.push_back(SkippedFile.getName().str()); |
321 | 0 | } |