llvm-project
321 строка · 11.2 Кб
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"
17using namespace clang;
18
19namespace {
20class HeaderIncludesCallback : public PPCallbacks {
21SourceManager &SM;
22raw_ostream *OutputFile;
23const DependencyOutputOptions &DepOpts;
24unsigned CurrentIncludeDepth;
25bool HasProcessedPredefines;
26bool OwnsOutputFile;
27bool ShowAllHeaders;
28bool ShowDepth;
29bool MSStyle;
30
31public:
32HeaderIncludesCallback(const Preprocessor *PP, bool ShowAllHeaders_,
33raw_ostream *OutputFile_,
34const DependencyOutputOptions &DepOpts,
35bool OwnsOutputFile_, bool ShowDepth_, bool MSStyle_)
36: SM(PP->getSourceManager()), OutputFile(OutputFile_), DepOpts(DepOpts),
37CurrentIncludeDepth(0), HasProcessedPredefines(false),
38OwnsOutputFile(OwnsOutputFile_), ShowAllHeaders(ShowAllHeaders_),
39ShowDepth(ShowDepth_), MSStyle(MSStyle_) {}
40
41~HeaderIncludesCallback() override {
42if (OwnsOutputFile)
43delete OutputFile;
44}
45
46HeaderIncludesCallback(const HeaderIncludesCallback &) = delete;
47HeaderIncludesCallback &operator=(const HeaderIncludesCallback &) = delete;
48
49void FileChanged(SourceLocation Loc, FileChangeReason Reason,
50SrcMgr::CharacteristicKind FileType,
51FileID PrevFID) override;
52
53void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
54SrcMgr::CharacteristicKind FileType) override;
55
56private:
57bool ShouldShowHeader(SrcMgr::CharacteristicKind HeaderType) {
58if (!DepOpts.IncludeSystemHeaders && isSystem(HeaderType))
59return 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.
64return (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.
79class HeaderIncludesJSONCallback : public PPCallbacks {
80SourceManager &SM;
81raw_ostream *OutputFile;
82bool OwnsOutputFile;
83SmallVector<std::string, 16> IncludedHeaders;
84
85public:
86HeaderIncludesJSONCallback(const Preprocessor *PP, raw_ostream *OutputFile_,
87bool OwnsOutputFile_)
88: SM(PP->getSourceManager()), OutputFile(OutputFile_),
89OwnsOutputFile(OwnsOutputFile_) {}
90
91~HeaderIncludesJSONCallback() override {
92if (OwnsOutputFile)
93delete OutputFile;
94}
95
96HeaderIncludesJSONCallback(const HeaderIncludesJSONCallback &) = delete;
97HeaderIncludesJSONCallback &
98operator=(const HeaderIncludesJSONCallback &) = delete;
99
100void EndOfMainFile() override;
101
102void FileChanged(SourceLocation Loc, FileChangeReason Reason,
103SrcMgr::CharacteristicKind FileType,
104FileID PrevFID) override;
105
106void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
107SrcMgr::CharacteristicKind FileType) override;
108};
109}
110
111static void PrintHeaderInfo(raw_ostream *OutputFile, StringRef Filename,
112bool ShowDepth, unsigned CurrentIncludeDepth,
113bool MSStyle) {
114// Write to a temporary string to avoid unnecessary flushing on errs().
115SmallString<512> Pathname(Filename);
116if (!MSStyle)
117Lexer::Stringify(Pathname);
118
119SmallString<256> Msg;
120if (MSStyle)
121Msg += "Note: including file:";
122
123if (ShowDepth) {
124// The main source file is at depth 1, so skip one dot.
125for (unsigned i = 1; i != CurrentIncludeDepth; ++i)
126Msg += MSStyle ? ' ' : '.';
127
128if (!MSStyle)
129Msg += ' ';
130}
131Msg += Pathname;
132Msg += '\n';
133
134*OutputFile << Msg;
135OutputFile->flush();
136}
137
138void clang::AttachHeaderIncludeGen(Preprocessor &PP,
139const DependencyOutputOptions &DepOpts,
140bool ShowAllHeaders, StringRef OutputPath,
141bool ShowDepth, bool MSStyle) {
142raw_ostream *OutputFile = &llvm::errs();
143bool OwnsOutputFile = false;
144
145// Choose output stream, when printing in cl.exe /showIncludes style.
146if (MSStyle) {
147switch (DepOpts.ShowIncludesDest) {
148default:
149llvm_unreachable("Invalid destination for /showIncludes output!");
150case ShowIncludesDestination::Stderr:
151OutputFile = &llvm::errs();
152break;
153case ShowIncludesDestination::Stdout:
154OutputFile = &llvm::outs();
155break;
156}
157}
158
159// Open the output file, if used.
160if (!OutputPath.empty()) {
161std::error_code EC;
162llvm::raw_fd_ostream *OS = new llvm::raw_fd_ostream(
163OutputPath.str(), EC,
164llvm::sys::fs::OF_Append | llvm::sys::fs::OF_TextWithCRLF);
165if (EC) {
166PP.getDiagnostics().Report(clang::diag::warn_fe_cc_print_header_failure)
167<< EC.message();
168delete OS;
169} else {
170OS->SetUnbuffered();
171OutputFile = OS;
172OwnsOutputFile = true;
173}
174}
175
176switch (DepOpts.HeaderIncludeFormat) {
177case HIFMT_None:
178llvm_unreachable("unexpected header format kind");
179case HIFMT_Textual: {
180assert(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.
188for (const auto &Header : DepOpts.ExtraDeps)
189PrintHeaderInfo(OutputFile, Header.first, ShowDepth, 2, MSStyle);
190PP.addPPCallbacks(std::make_unique<HeaderIncludesCallback>(
191&PP, ShowAllHeaders, OutputFile, DepOpts, OwnsOutputFile, ShowDepth,
192MSStyle));
193break;
194}
195case HIFMT_JSON: {
196assert(DepOpts.HeaderIncludeFiltering == HIFIL_Only_Direct_System &&
197"only-direct-system is the only option for filtering");
198PP.addPPCallbacks(std::make_unique<HeaderIncludesJSONCallback>(
199&PP, OutputFile, OwnsOutputFile));
200break;
201}
202}
203}
204
205void HeaderIncludesCallback::FileChanged(SourceLocation Loc,
206FileChangeReason Reason,
207SrcMgr::CharacteristicKind NewFileType,
208FileID PrevFID) {
209// Unless we are exiting a #include, make sure to skip ahead to the line the
210// #include directive was at.
211PresumedLoc UserLoc = SM.getPresumedLoc(Loc);
212if (UserLoc.isInvalid())
213return;
214
215// Adjust the current include depth.
216if (Reason == PPCallbacks::EnterFile) {
217++CurrentIncludeDepth;
218} else if (Reason == PPCallbacks::ExitFile) {
219if (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.
224if (CurrentIncludeDepth == 1 && !HasProcessedPredefines)
225HasProcessedPredefines = true;
226
227return;
228} else {
229return;
230}
231
232if (!ShouldShowHeader(NewFileType))
233return;
234
235unsigned IncludeDepth = CurrentIncludeDepth;
236if (!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.
241if (Reason == PPCallbacks::EnterFile &&
242UserLoc.getFilename() != StringRef("<command line>")) {
243PrintHeaderInfo(OutputFile, UserLoc.getFilename(), ShowDepth, IncludeDepth,
244MSStyle);
245}
246}
247
248void HeaderIncludesCallback::FileSkipped(const FileEntryRef &SkippedFile, const
249Token &FilenameTok,
250SrcMgr::CharacteristicKind FileType) {
251if (!DepOpts.ShowSkippedHeaderIncludes)
252return;
253
254if (!ShouldShowHeader(FileType))
255return;
256
257PrintHeaderInfo(OutputFile, SkippedFile.getName(), ShowDepth,
258CurrentIncludeDepth + 1, MSStyle);
259}
260
261void HeaderIncludesJSONCallback::EndOfMainFile() {
262OptionalFileEntryRef FE = SM.getFileEntryRefForID(SM.getMainFileID());
263SmallString<256> MainFile(FE->getName());
264SM.getFileManager().makeAbsolutePath(MainFile);
265
266std::string Str;
267llvm::raw_string_ostream OS(Str);
268llvm::json::OStream JOS(OS);
269JOS.object([&] {
270JOS.attribute("source", MainFile.c_str());
271JOS.attributeArray("includes", [&] {
272llvm::StringSet<> SeenHeaders;
273for (const std::string &H : IncludedHeaders)
274if (SeenHeaders.insert(H).second)
275JOS.value(H);
276});
277});
278OS << "\n";
279
280if (OutputFile->get_kind() == raw_ostream::OStreamKind::OK_FDStream) {
281llvm::raw_fd_ostream *FDS = static_cast<llvm::raw_fd_ostream *>(OutputFile);
282if (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.
291static bool shouldRecordNewFile(SrcMgr::CharacteristicKind NewFileType,
292SourceLocation PrevLoc, SourceManager &SM) {
293return SrcMgr::isSystem(NewFileType) && !SM.isInSystemHeader(PrevLoc);
294}
295
296void HeaderIncludesJSONCallback::FileChanged(
297SourceLocation Loc, FileChangeReason Reason,
298SrcMgr::CharacteristicKind NewFileType, FileID PrevFID) {
299if (PrevFID.isInvalid() ||
300!shouldRecordNewFile(NewFileType, SM.getLocForStartOfFile(PrevFID), SM))
301return;
302
303// Unless we are exiting a #include, make sure to skip ahead to the line the
304// #include directive was at.
305PresumedLoc UserLoc = SM.getPresumedLoc(Loc);
306if (UserLoc.isInvalid())
307return;
308
309if (Reason == PPCallbacks::EnterFile &&
310UserLoc.getFilename() != StringRef("<command line>"))
311IncludedHeaders.push_back(UserLoc.getFilename());
312}
313
314void HeaderIncludesJSONCallback::FileSkipped(
315const FileEntryRef &SkippedFile, const Token &FilenameTok,
316SrcMgr::CharacteristicKind FileType) {
317if (!shouldRecordNewFile(FileType, FilenameTok.getLocation(), SM))
318return;
319
320IncludedHeaders.push_back(SkippedFile.getName().str());
321}
322