Coverage Report

Created: 2024-01-17 10:31

/src/llvm-project/clang/lib/Frontend/DependencyFile.cpp
Line
Count
Source (jump to first uncovered line)
1
//===--- DependencyFile.cpp - Generate dependency file --------------------===//
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
// This code generates dependency files.
10
//
11
//===----------------------------------------------------------------------===//
12
13
#include "clang/Frontend/Utils.h"
14
#include "clang/Basic/FileManager.h"
15
#include "clang/Basic/SourceManager.h"
16
#include "clang/Frontend/DependencyOutputOptions.h"
17
#include "clang/Frontend/FrontendDiagnostic.h"
18
#include "clang/Lex/DirectoryLookup.h"
19
#include "clang/Lex/ModuleMap.h"
20
#include "clang/Lex/PPCallbacks.h"
21
#include "clang/Lex/Preprocessor.h"
22
#include "clang/Serialization/ASTReader.h"
23
#include "llvm/ADT/StringSet.h"
24
#include "llvm/Support/FileSystem.h"
25
#include "llvm/Support/Path.h"
26
#include "llvm/Support/raw_ostream.h"
27
#include <optional>
28
29
using namespace clang;
30
31
namespace {
32
struct DepCollectorPPCallbacks : public PPCallbacks {
33
  DependencyCollector &DepCollector;
34
  Preprocessor &PP;
35
  DepCollectorPPCallbacks(DependencyCollector &L, Preprocessor &PP)
36
0
      : DepCollector(L), PP(PP) {}
37
38
  void LexedFileChanged(FileID FID, LexedFileChangeReason Reason,
39
                        SrcMgr::CharacteristicKind FileType, FileID PrevFID,
40
0
                        SourceLocation Loc) override {
41
0
    if (Reason != PPCallbacks::LexedFileChangeReason::EnterFile)
42
0
      return;
43
44
    // Dependency generation really does want to go all the way to the
45
    // file entry for a source location to find out what is depended on.
46
    // We do not want #line markers to affect dependency generation!
47
0
    if (std::optional<StringRef> Filename =
48
0
            PP.getSourceManager().getNonBuiltinFilenameForID(FID))
49
0
      DepCollector.maybeAddDependency(
50
0
          llvm::sys::path::remove_leading_dotslash(*Filename),
51
0
          /*FromModule*/ false, isSystem(FileType), /*IsModuleFile*/ false,
52
0
          /*IsMissing*/ false);
53
0
  }
54
55
  void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
56
0
                   SrcMgr::CharacteristicKind FileType) override {
57
0
    StringRef Filename =
58
0
        llvm::sys::path::remove_leading_dotslash(SkippedFile.getName());
59
0
    DepCollector.maybeAddDependency(Filename, /*FromModule=*/false,
60
0
                                    /*IsSystem=*/isSystem(FileType),
61
0
                                    /*IsModuleFile=*/false,
62
0
                                    /*IsMissing=*/false);
63
0
  }
64
65
  void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
66
                          StringRef FileName, bool IsAngled,
67
                          CharSourceRange FilenameRange,
68
                          OptionalFileEntryRef File, StringRef SearchPath,
69
                          StringRef RelativePath, const Module *Imported,
70
0
                          SrcMgr::CharacteristicKind FileType) override {
71
0
    if (!File)
72
0
      DepCollector.maybeAddDependency(FileName, /*FromModule*/ false,
73
0
                                      /*IsSystem*/ false,
74
0
                                      /*IsModuleFile*/ false,
75
0
                                      /*IsMissing*/ true);
76
    // Files that actually exist are handled by FileChanged.
77
0
  }
78
79
  void HasInclude(SourceLocation Loc, StringRef SpelledFilename, bool IsAngled,
80
                  OptionalFileEntryRef File,
81
0
                  SrcMgr::CharacteristicKind FileType) override {
82
0
    if (!File)
83
0
      return;
84
0
    StringRef Filename =
85
0
        llvm::sys::path::remove_leading_dotslash(File->getName());
86
0
    DepCollector.maybeAddDependency(Filename, /*FromModule=*/false,
87
0
                                    /*IsSystem=*/isSystem(FileType),
88
0
                                    /*IsModuleFile=*/false,
89
0
                                    /*IsMissing=*/false);
90
0
  }
91
92
0
  void EndOfMainFile() override {
93
0
    DepCollector.finishedMainFile(PP.getDiagnostics());
94
0
  }
95
};
96
97
struct DepCollectorMMCallbacks : public ModuleMapCallbacks {
98
  DependencyCollector &DepCollector;
99
0
  DepCollectorMMCallbacks(DependencyCollector &DC) : DepCollector(DC) {}
100
101
  void moduleMapFileRead(SourceLocation Loc, FileEntryRef Entry,
102
0
                         bool IsSystem) override {
103
0
    StringRef Filename = Entry.getName();
104
0
    DepCollector.maybeAddDependency(Filename, /*FromModule*/ false,
105
0
                                    /*IsSystem*/ IsSystem,
106
0
                                    /*IsModuleFile*/ false,
107
0
                                    /*IsMissing*/ false);
108
0
  }
109
};
110
111
struct DepCollectorASTListener : public ASTReaderListener {
112
  DependencyCollector &DepCollector;
113
  FileManager &FileMgr;
114
  DepCollectorASTListener(DependencyCollector &L, FileManager &FileMgr)
115
0
      : DepCollector(L), FileMgr(FileMgr) {}
116
0
  bool needsInputFileVisitation() override { return true; }
117
0
  bool needsSystemInputFileVisitation() override {
118
0
    return DepCollector.needSystemDependencies();
119
0
  }
120
  void visitModuleFile(StringRef Filename,
121
0
                       serialization::ModuleKind Kind) override {
122
0
    DepCollector.maybeAddDependency(Filename, /*FromModule*/ true,
123
0
                                    /*IsSystem*/ false, /*IsModuleFile*/ true,
124
0
                                    /*IsMissing*/ false);
125
0
  }
126
  bool visitInputFile(StringRef Filename, bool IsSystem,
127
0
                      bool IsOverridden, bool IsExplicitModule) override {
128
0
    if (IsOverridden || IsExplicitModule)
129
0
      return true;
130
131
    // Run this through the FileManager in order to respect 'use-external-name'
132
    // in case we have a VFS overlay.
133
0
    if (auto FE = FileMgr.getOptionalFileRef(Filename))
134
0
      Filename = FE->getName();
135
136
0
    DepCollector.maybeAddDependency(Filename, /*FromModule*/ true, IsSystem,
137
0
                                    /*IsModuleFile*/ false,
138
0
                                    /*IsMissing*/ false);
139
0
    return true;
140
0
  }
141
};
142
} // end anonymous namespace
143
144
void DependencyCollector::maybeAddDependency(StringRef Filename,
145
                                             bool FromModule, bool IsSystem,
146
                                             bool IsModuleFile,
147
0
                                             bool IsMissing) {
148
0
  if (sawDependency(Filename, FromModule, IsSystem, IsModuleFile, IsMissing))
149
0
    addDependency(Filename);
150
0
}
151
152
0
bool DependencyCollector::addDependency(StringRef Filename) {
153
0
  StringRef SearchPath;
154
#ifdef _WIN32
155
  // Make the search insensitive to case and separators.
156
  llvm::SmallString<256> TmpPath = Filename;
157
  llvm::sys::path::native(TmpPath);
158
  std::transform(TmpPath.begin(), TmpPath.end(), TmpPath.begin(), ::tolower);
159
  SearchPath = TmpPath.str();
160
#else
161
0
  SearchPath = Filename;
162
0
#endif
163
164
0
  if (Seen.insert(SearchPath).second) {
165
0
    Dependencies.push_back(std::string(Filename));
166
0
    return true;
167
0
  }
168
0
  return false;
169
0
}
170
171
0
static bool isSpecialFilename(StringRef Filename) {
172
0
  return Filename == "<built-in>";
173
0
}
174
175
bool DependencyCollector::sawDependency(StringRef Filename, bool FromModule,
176
                                        bool IsSystem, bool IsModuleFile,
177
0
                                        bool IsMissing) {
178
0
  return !isSpecialFilename(Filename) &&
179
0
         (needSystemDependencies() || !IsSystem);
180
0
}
181
182
0
DependencyCollector::~DependencyCollector() { }
183
0
void DependencyCollector::attachToPreprocessor(Preprocessor &PP) {
184
0
  PP.addPPCallbacks(std::make_unique<DepCollectorPPCallbacks>(*this, PP));
185
0
  PP.getHeaderSearchInfo().getModuleMap().addModuleMapCallbacks(
186
0
      std::make_unique<DepCollectorMMCallbacks>(*this));
187
0
}
188
0
void DependencyCollector::attachToASTReader(ASTReader &R) {
189
0
  R.addListener(
190
0
      std::make_unique<DepCollectorASTListener>(*this, R.getFileManager()));
191
0
}
192
193
DependencyFileGenerator::DependencyFileGenerator(
194
    const DependencyOutputOptions &Opts)
195
    : OutputFile(Opts.OutputFile), Targets(Opts.Targets),
196
      IncludeSystemHeaders(Opts.IncludeSystemHeaders),
197
      PhonyTarget(Opts.UsePhonyTargets),
198
      AddMissingHeaderDeps(Opts.AddMissingHeaderDeps), SeenMissingHeader(false),
199
      IncludeModuleFiles(Opts.IncludeModuleFiles),
200
0
      OutputFormat(Opts.OutputFormat), InputFileIndex(0) {
201
0
  for (const auto &ExtraDep : Opts.ExtraDeps) {
202
0
    if (addDependency(ExtraDep.first))
203
0
      ++InputFileIndex;
204
0
  }
205
0
}
206
207
0
void DependencyFileGenerator::attachToPreprocessor(Preprocessor &PP) {
208
  // Disable the "file not found" diagnostic if the -MG option was given.
209
0
  if (AddMissingHeaderDeps)
210
0
    PP.SetSuppressIncludeNotFoundError(true);
211
212
0
  DependencyCollector::attachToPreprocessor(PP);
213
0
}
214
215
bool DependencyFileGenerator::sawDependency(StringRef Filename, bool FromModule,
216
                                            bool IsSystem, bool IsModuleFile,
217
0
                                            bool IsMissing) {
218
0
  if (IsMissing) {
219
    // Handle the case of missing file from an inclusion directive.
220
0
    if (AddMissingHeaderDeps)
221
0
      return true;
222
0
    SeenMissingHeader = true;
223
0
    return false;
224
0
  }
225
0
  if (IsModuleFile && !IncludeModuleFiles)
226
0
    return false;
227
228
0
  if (isSpecialFilename(Filename))
229
0
    return false;
230
231
0
  if (IncludeSystemHeaders)
232
0
    return true;
233
234
0
  return !IsSystem;
235
0
}
236
237
0
void DependencyFileGenerator::finishedMainFile(DiagnosticsEngine &Diags) {
238
0
  outputDependencyFile(Diags);
239
0
}
240
241
/// Print the filename, with escaping or quoting that accommodates the three
242
/// most likely tools that use dependency files: GNU Make, BSD Make, and
243
/// NMake/Jom.
244
///
245
/// BSD Make is the simplest case: It does no escaping at all.  This means
246
/// characters that are normally delimiters, i.e. space and # (the comment
247
/// character) simply aren't supported in filenames.
248
///
249
/// GNU Make does allow space and # in filenames, but to avoid being treated
250
/// as a delimiter or comment, these must be escaped with a backslash. Because
251
/// backslash is itself the escape character, if a backslash appears in a
252
/// filename, it should be escaped as well.  (As a special case, $ is escaped
253
/// as $$, which is the normal Make way to handle the $ character.)
254
/// For compatibility with BSD Make and historical practice, if GNU Make
255
/// un-escapes characters in a filename but doesn't find a match, it will
256
/// retry with the unmodified original string.
257
///
258
/// GCC tries to accommodate both Make formats by escaping any space or #
259
/// characters in the original filename, but not escaping backslashes.  The
260
/// apparent intent is so that filenames with backslashes will be handled
261
/// correctly by BSD Make, and by GNU Make in its fallback mode of using the
262
/// unmodified original string; filenames with # or space characters aren't
263
/// supported by BSD Make at all, but will be handled correctly by GNU Make
264
/// due to the escaping.
265
///
266
/// A corner case that GCC gets only partly right is when the original filename
267
/// has a backslash immediately followed by space or #.  GNU Make would expect
268
/// this backslash to be escaped; however GCC escapes the original backslash
269
/// only when followed by space, not #.  It will therefore take a dependency
270
/// from a directive such as
271
///     #include "a\ b\#c.h"
272
/// and emit it as
273
///     a\\\ b\\#c.h
274
/// which GNU Make will interpret as
275
///     a\ b\
276
/// followed by a comment. Failing to find this file, it will fall back to the
277
/// original string, which probably doesn't exist either; in any case it won't
278
/// find
279
///     a\ b\#c.h
280
/// which is the actual filename specified by the include directive.
281
///
282
/// Clang does what GCC does, rather than what GNU Make expects.
283
///
284
/// NMake/Jom has a different set of scary characters, but wraps filespecs in
285
/// double-quotes to avoid misinterpreting them; see
286
/// https://msdn.microsoft.com/en-us/library/dd9y37ha.aspx for NMake info,
287
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
288
/// for Windows file-naming info.
289
static void PrintFilename(raw_ostream &OS, StringRef Filename,
290
0
                          DependencyOutputFormat OutputFormat) {
291
  // Convert filename to platform native path
292
0
  llvm::SmallString<256> NativePath;
293
0
  llvm::sys::path::native(Filename.str(), NativePath);
294
295
0
  if (OutputFormat == DependencyOutputFormat::NMake) {
296
    // Add quotes if needed. These are the characters listed as "special" to
297
    // NMake, that are legal in a Windows filespec, and that could cause
298
    // misinterpretation of the dependency string.
299
0
    if (NativePath.find_first_of(" #${}^!") != StringRef::npos)
300
0
      OS << '\"' << NativePath << '\"';
301
0
    else
302
0
      OS << NativePath;
303
0
    return;
304
0
  }
305
0
  assert(OutputFormat == DependencyOutputFormat::Make);
306
0
  for (unsigned i = 0, e = NativePath.size(); i != e; ++i) {
307
0
    if (NativePath[i] == '#') // Handle '#' the broken gcc way.
308
0
      OS << '\\';
309
0
    else if (NativePath[i] == ' ') { // Handle space correctly.
310
0
      OS << '\\';
311
0
      unsigned j = i;
312
0
      while (j > 0 && NativePath[--j] == '\\')
313
0
        OS << '\\';
314
0
    } else if (NativePath[i] == '$') // $ is escaped by $$.
315
0
      OS << '$';
316
0
    OS << NativePath[i];
317
0
  }
318
0
}
319
320
0
void DependencyFileGenerator::outputDependencyFile(DiagnosticsEngine &Diags) {
321
0
  if (SeenMissingHeader) {
322
0
    llvm::sys::fs::remove(OutputFile);
323
0
    return;
324
0
  }
325
326
0
  std::error_code EC;
327
0
  llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF);
328
0
  if (EC) {
329
0
    Diags.Report(diag::err_fe_error_opening) << OutputFile << EC.message();
330
0
    return;
331
0
  }
332
333
0
  outputDependencyFile(OS);
334
0
}
335
336
0
void DependencyFileGenerator::outputDependencyFile(llvm::raw_ostream &OS) {
337
  // Write out the dependency targets, trying to avoid overly long
338
  // lines when possible. We try our best to emit exactly the same
339
  // dependency file as GCC>=10, assuming the included files are the
340
  // same.
341
0
  const unsigned MaxColumns = 75;
342
0
  unsigned Columns = 0;
343
344
0
  for (StringRef Target : Targets) {
345
0
    unsigned N = Target.size();
346
0
    if (Columns == 0) {
347
0
      Columns += N;
348
0
    } else if (Columns + N + 2 > MaxColumns) {
349
0
      Columns = N + 2;
350
0
      OS << " \\\n  ";
351
0
    } else {
352
0
      Columns += N + 1;
353
0
      OS << ' ';
354
0
    }
355
    // Targets already quoted as needed.
356
0
    OS << Target;
357
0
  }
358
359
0
  OS << ':';
360
0
  Columns += 1;
361
362
  // Now add each dependency in the order it was seen, but avoiding
363
  // duplicates.
364
0
  ArrayRef<std::string> Files = getDependencies();
365
0
  for (StringRef File : Files) {
366
0
    if (File == "<stdin>")
367
0
      continue;
368
    // Start a new line if this would exceed the column limit. Make
369
    // sure to leave space for a trailing " \" in case we need to
370
    // break the line on the next iteration.
371
0
    unsigned N = File.size();
372
0
    if (Columns + (N + 1) + 2 > MaxColumns) {
373
0
      OS << " \\\n ";
374
0
      Columns = 2;
375
0
    }
376
0
    OS << ' ';
377
0
    PrintFilename(OS, File, OutputFormat);
378
0
    Columns += N + 1;
379
0
  }
380
0
  OS << '\n';
381
382
  // Create phony targets if requested.
383
0
  if (PhonyTarget && !Files.empty()) {
384
0
    unsigned Index = 0;
385
0
    for (auto I = Files.begin(), E = Files.end(); I != E; ++I) {
386
0
      if (Index++ == InputFileIndex)
387
0
        continue;
388
0
      PrintFilename(OS, *I, OutputFormat);
389
0
      OS << ":\n";
390
0
    }
391
0
  }
392
0
}