Coverage Report

Created: 2024-01-17 10:31

/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
}