xref: /freebsd/contrib/llvm-project/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
1 //===- DependencyScanningWorker.cpp - clang-scan-deps worker --------------===//
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/Tooling/DependencyScanning/DependencyScanningWorker.h"
10 #include "clang/Basic/DiagnosticDriver.h"
11 #include "clang/Basic/DiagnosticFrontend.h"
12 #include "clang/Basic/DiagnosticSerialization.h"
13 #include "clang/Driver/Compilation.h"
14 #include "clang/Driver/Driver.h"
15 #include "clang/Driver/Job.h"
16 #include "clang/Driver/Tool.h"
17 #include "clang/Frontend/CompilerInstance.h"
18 #include "clang/Frontend/CompilerInvocation.h"
19 #include "clang/Frontend/FrontendActions.h"
20 #include "clang/Frontend/TextDiagnosticPrinter.h"
21 #include "clang/Frontend/Utils.h"
22 #include "clang/Lex/PreprocessorOptions.h"
23 #include "clang/Serialization/ObjectFilePCHContainerReader.h"
24 #include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
25 #include "clang/Tooling/DependencyScanning/InProcessModuleCache.h"
26 #include "clang/Tooling/DependencyScanning/ModuleDepCollector.h"
27 #include "clang/Tooling/Tooling.h"
28 #include "llvm/ADT/IntrusiveRefCntPtr.h"
29 #include "llvm/Support/Allocator.h"
30 #include "llvm/Support/Error.h"
31 #include "llvm/Support/MemoryBuffer.h"
32 #include "llvm/TargetParser/Host.h"
33 #include <optional>
34 
35 using namespace clang;
36 using namespace tooling;
37 using namespace dependencies;
38 
39 namespace {
40 
41 /// Forwards the gatherered dependencies to the consumer.
42 class DependencyConsumerForwarder : public DependencyFileGenerator {
43 public:
DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> Opts,StringRef WorkingDirectory,DependencyConsumer & C)44   DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> Opts,
45                               StringRef WorkingDirectory, DependencyConsumer &C)
46       : DependencyFileGenerator(*Opts), WorkingDirectory(WorkingDirectory),
47         Opts(std::move(Opts)), C(C) {}
48 
finishedMainFile(DiagnosticsEngine & Diags)49   void finishedMainFile(DiagnosticsEngine &Diags) override {
50     C.handleDependencyOutputOpts(*Opts);
51     llvm::SmallString<256> CanonPath;
52     for (const auto &File : getDependencies()) {
53       CanonPath = File;
54       llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true);
55       llvm::sys::fs::make_absolute(WorkingDirectory, CanonPath);
56       C.handleFileDependency(CanonPath);
57     }
58   }
59 
60 private:
61   StringRef WorkingDirectory;
62   std::unique_ptr<DependencyOutputOptions> Opts;
63   DependencyConsumer &C;
64 };
65 
checkHeaderSearchPaths(const HeaderSearchOptions & HSOpts,const HeaderSearchOptions & ExistingHSOpts,DiagnosticsEngine * Diags,const LangOptions & LangOpts)66 static bool checkHeaderSearchPaths(const HeaderSearchOptions &HSOpts,
67                                    const HeaderSearchOptions &ExistingHSOpts,
68                                    DiagnosticsEngine *Diags,
69                                    const LangOptions &LangOpts) {
70   if (LangOpts.Modules) {
71     if (HSOpts.VFSOverlayFiles != ExistingHSOpts.VFSOverlayFiles) {
72       if (Diags) {
73         Diags->Report(diag::warn_pch_vfsoverlay_mismatch);
74         auto VFSNote = [&](int Type, ArrayRef<std::string> VFSOverlays) {
75           if (VFSOverlays.empty()) {
76             Diags->Report(diag::note_pch_vfsoverlay_empty) << Type;
77           } else {
78             std::string Files = llvm::join(VFSOverlays, "\n");
79             Diags->Report(diag::note_pch_vfsoverlay_files) << Type << Files;
80           }
81         };
82         VFSNote(0, HSOpts.VFSOverlayFiles);
83         VFSNote(1, ExistingHSOpts.VFSOverlayFiles);
84       }
85     }
86   }
87   return false;
88 }
89 
90 using PrebuiltModuleFilesT = decltype(HeaderSearchOptions::PrebuiltModuleFiles);
91 
92 /// A listener that collects the imported modules and the input
93 /// files. While visiting, collect vfsoverlays and file inputs that determine
94 /// whether prebuilt modules fully resolve in stable directories.
95 class PrebuiltModuleListener : public ASTReaderListener {
96 public:
PrebuiltModuleListener(PrebuiltModuleFilesT & PrebuiltModuleFiles,llvm::SmallVector<std::string> & NewModuleFiles,PrebuiltModulesAttrsMap & PrebuiltModulesASTMap,const HeaderSearchOptions & HSOpts,const LangOptions & LangOpts,DiagnosticsEngine & Diags,const ArrayRef<StringRef> StableDirs)97   PrebuiltModuleListener(PrebuiltModuleFilesT &PrebuiltModuleFiles,
98                          llvm::SmallVector<std::string> &NewModuleFiles,
99                          PrebuiltModulesAttrsMap &PrebuiltModulesASTMap,
100                          const HeaderSearchOptions &HSOpts,
101                          const LangOptions &LangOpts, DiagnosticsEngine &Diags,
102                          const ArrayRef<StringRef> StableDirs)
103       : PrebuiltModuleFiles(PrebuiltModuleFiles),
104         NewModuleFiles(NewModuleFiles),
105         PrebuiltModulesASTMap(PrebuiltModulesASTMap), ExistingHSOpts(HSOpts),
106         ExistingLangOpts(LangOpts), Diags(Diags), StableDirs(StableDirs) {}
107 
needsImportVisitation() const108   bool needsImportVisitation() const override { return true; }
needsInputFileVisitation()109   bool needsInputFileVisitation() override { return true; }
needsSystemInputFileVisitation()110   bool needsSystemInputFileVisitation() override { return true; }
111 
112   /// Accumulate the modules are transitively depended on by the initial
113   /// prebuilt module.
visitImport(StringRef ModuleName,StringRef Filename)114   void visitImport(StringRef ModuleName, StringRef Filename) override {
115     if (PrebuiltModuleFiles.insert({ModuleName.str(), Filename.str()}).second)
116       NewModuleFiles.push_back(Filename.str());
117 
118     auto PrebuiltMapEntry = PrebuiltModulesASTMap.try_emplace(Filename);
119     PrebuiltModuleASTAttrs &PrebuiltModule = PrebuiltMapEntry.first->second;
120     if (PrebuiltMapEntry.second)
121       PrebuiltModule.setInStableDir(!StableDirs.empty());
122 
123     if (auto It = PrebuiltModulesASTMap.find(CurrentFile);
124         It != PrebuiltModulesASTMap.end() && CurrentFile != Filename)
125       PrebuiltModule.addDependent(It->getKey());
126   }
127 
128   /// For each input file discovered, check whether it's external path is in a
129   /// stable directory. Traversal is stopped if the current module is not
130   /// considered stable.
visitInputFile(StringRef FilenameAsRequested,StringRef Filename,bool isSystem,bool isOverridden,bool isExplicitModule)131   bool visitInputFile(StringRef FilenameAsRequested, StringRef Filename,
132                       bool isSystem, bool isOverridden,
133                       bool isExplicitModule) override {
134     if (StableDirs.empty())
135       return false;
136     auto PrebuiltEntryIt = PrebuiltModulesASTMap.find(CurrentFile);
137     if ((PrebuiltEntryIt == PrebuiltModulesASTMap.end()) ||
138         (!PrebuiltEntryIt->second.isInStableDir()))
139       return false;
140 
141     PrebuiltEntryIt->second.setInStableDir(
142         isPathInStableDir(StableDirs, Filename));
143     return PrebuiltEntryIt->second.isInStableDir();
144   }
145 
146   /// Update which module that is being actively traversed.
visitModuleFile(StringRef Filename,serialization::ModuleKind Kind)147   void visitModuleFile(StringRef Filename,
148                        serialization::ModuleKind Kind) override {
149     // If the CurrentFile is not
150     // considered stable, update any of it's transitive dependents.
151     auto PrebuiltEntryIt = PrebuiltModulesASTMap.find(CurrentFile);
152     if ((PrebuiltEntryIt != PrebuiltModulesASTMap.end()) &&
153         !PrebuiltEntryIt->second.isInStableDir())
154       PrebuiltEntryIt->second.updateDependentsNotInStableDirs(
155           PrebuiltModulesASTMap);
156     CurrentFile = Filename;
157   }
158 
159   /// Check the header search options for a given module when considering
160   /// if the module comes from stable directories.
ReadHeaderSearchOptions(const HeaderSearchOptions & HSOpts,StringRef ModuleFilename,StringRef SpecificModuleCachePath,bool Complain)161   bool ReadHeaderSearchOptions(const HeaderSearchOptions &HSOpts,
162                                StringRef ModuleFilename,
163                                StringRef SpecificModuleCachePath,
164                                bool Complain) override {
165 
166     auto PrebuiltMapEntry = PrebuiltModulesASTMap.try_emplace(CurrentFile);
167     PrebuiltModuleASTAttrs &PrebuiltModule = PrebuiltMapEntry.first->second;
168     if (PrebuiltMapEntry.second)
169       PrebuiltModule.setInStableDir(!StableDirs.empty());
170 
171     if (PrebuiltModule.isInStableDir())
172       PrebuiltModule.setInStableDir(areOptionsInStableDir(StableDirs, HSOpts));
173 
174     return false;
175   }
176 
177   /// Accumulate vfsoverlays used to build these prebuilt modules.
ReadHeaderSearchPaths(const HeaderSearchOptions & HSOpts,bool Complain)178   bool ReadHeaderSearchPaths(const HeaderSearchOptions &HSOpts,
179                              bool Complain) override {
180 
181     auto PrebuiltMapEntry = PrebuiltModulesASTMap.try_emplace(CurrentFile);
182     PrebuiltModuleASTAttrs &PrebuiltModule = PrebuiltMapEntry.first->second;
183     if (PrebuiltMapEntry.second)
184       PrebuiltModule.setInStableDir(!StableDirs.empty());
185 
186     PrebuiltModule.setVFS(
187         llvm::StringSet<>(llvm::from_range, HSOpts.VFSOverlayFiles));
188 
189     return checkHeaderSearchPaths(
190         HSOpts, ExistingHSOpts, Complain ? &Diags : nullptr, ExistingLangOpts);
191   }
192 
193 private:
194   PrebuiltModuleFilesT &PrebuiltModuleFiles;
195   llvm::SmallVector<std::string> &NewModuleFiles;
196   PrebuiltModulesAttrsMap &PrebuiltModulesASTMap;
197   const HeaderSearchOptions &ExistingHSOpts;
198   const LangOptions &ExistingLangOpts;
199   DiagnosticsEngine &Diags;
200   std::string CurrentFile;
201   const ArrayRef<StringRef> StableDirs;
202 };
203 
204 /// Visit the given prebuilt module and collect all of the modules it
205 /// transitively imports and contributing input files.
visitPrebuiltModule(StringRef PrebuiltModuleFilename,CompilerInstance & CI,PrebuiltModuleFilesT & ModuleFiles,PrebuiltModulesAttrsMap & PrebuiltModulesASTMap,DiagnosticsEngine & Diags,const ArrayRef<StringRef> StableDirs)206 static bool visitPrebuiltModule(StringRef PrebuiltModuleFilename,
207                                 CompilerInstance &CI,
208                                 PrebuiltModuleFilesT &ModuleFiles,
209                                 PrebuiltModulesAttrsMap &PrebuiltModulesASTMap,
210                                 DiagnosticsEngine &Diags,
211                                 const ArrayRef<StringRef> StableDirs) {
212   // List of module files to be processed.
213   llvm::SmallVector<std::string> Worklist;
214 
215   PrebuiltModuleListener Listener(ModuleFiles, Worklist, PrebuiltModulesASTMap,
216                                   CI.getHeaderSearchOpts(), CI.getLangOpts(),
217                                   Diags, StableDirs);
218 
219   Listener.visitModuleFile(PrebuiltModuleFilename,
220                            serialization::MK_ExplicitModule);
221   if (ASTReader::readASTFileControlBlock(
222           PrebuiltModuleFilename, CI.getFileManager(), CI.getModuleCache(),
223           CI.getPCHContainerReader(),
224           /*FindModuleFileExtensions=*/false, Listener,
225           /*ValidateDiagnosticOptions=*/false, ASTReader::ARR_OutOfDate))
226     return true;
227 
228   while (!Worklist.empty()) {
229     Listener.visitModuleFile(Worklist.back(), serialization::MK_ExplicitModule);
230     if (ASTReader::readASTFileControlBlock(
231             Worklist.pop_back_val(), CI.getFileManager(), CI.getModuleCache(),
232             CI.getPCHContainerReader(),
233             /*FindModuleFileExtensions=*/false, Listener,
234             /*ValidateDiagnosticOptions=*/false))
235       return true;
236   }
237   return false;
238 }
239 
240 /// Transform arbitrary file name into an object-like file name.
makeObjFileName(StringRef FileName)241 static std::string makeObjFileName(StringRef FileName) {
242   SmallString<128> ObjFileName(FileName);
243   llvm::sys::path::replace_extension(ObjFileName, "o");
244   return std::string(ObjFileName);
245 }
246 
247 /// Deduce the dependency target based on the output file and input files.
248 static std::string
deduceDepTarget(const std::string & OutputFile,const SmallVectorImpl<FrontendInputFile> & InputFiles)249 deduceDepTarget(const std::string &OutputFile,
250                 const SmallVectorImpl<FrontendInputFile> &InputFiles) {
251   if (OutputFile != "-")
252     return OutputFile;
253 
254   if (InputFiles.empty() || !InputFiles.front().isFile())
255     return "clang-scan-deps\\ dependency";
256 
257   return makeObjFileName(InputFiles.front().getFile());
258 }
259 
260 /// Sanitize diagnostic options for dependency scan.
sanitizeDiagOpts(DiagnosticOptions & DiagOpts)261 static void sanitizeDiagOpts(DiagnosticOptions &DiagOpts) {
262   // Don't print 'X warnings and Y errors generated'.
263   DiagOpts.ShowCarets = false;
264   // Don't write out diagnostic file.
265   DiagOpts.DiagnosticSerializationFile.clear();
266   // Don't emit warnings except for scanning specific warnings.
267   // TODO: It would be useful to add a more principled way to ignore all
268   //       warnings that come from source code. The issue is that we need to
269   //       ignore warnings that could be surpressed by
270   //       `#pragma clang diagnostic`, while still allowing some scanning
271   //       warnings for things we're not ready to turn into errors yet.
272   //       See `test/ClangScanDeps/diagnostic-pragmas.c` for an example.
273   llvm::erase_if(DiagOpts.Warnings, [](StringRef Warning) {
274     return llvm::StringSwitch<bool>(Warning)
275         .Cases("pch-vfs-diff", "error=pch-vfs-diff", false)
276         .StartsWith("no-error=", false)
277         .Default(true);
278   });
279 }
280 
281 // Clang implements -D and -U by splatting text into a predefines buffer. This
282 // allows constructs such as `-DFඞ=3 "-D F\u{0D9E} 4 3 2”` to be accepted and
283 // define the same macro, or adding C++ style comments before the macro name.
284 //
285 // This function checks that the first non-space characters in the macro
286 // obviously form an identifier that can be uniqued on without lexing. Failing
287 // to do this could lead to changing the final definition of a macro.
288 //
289 // We could set up a preprocessor and actually lex the name, but that's very
290 // heavyweight for a situation that will almost never happen in practice.
getSimpleMacroName(StringRef Macro)291 static std::optional<StringRef> getSimpleMacroName(StringRef Macro) {
292   StringRef Name = Macro.split("=").first.ltrim(" \t");
293   std::size_t I = 0;
294 
295   auto FinishName = [&]() -> std::optional<StringRef> {
296     StringRef SimpleName = Name.slice(0, I);
297     if (SimpleName.empty())
298       return std::nullopt;
299     return SimpleName;
300   };
301 
302   for (; I != Name.size(); ++I) {
303     switch (Name[I]) {
304     case '(': // Start of macro parameter list
305     case ' ': // End of macro name
306     case '\t':
307       return FinishName();
308     case '_':
309       continue;
310     default:
311       if (llvm::isAlnum(Name[I]))
312         continue;
313       return std::nullopt;
314     }
315   }
316   return FinishName();
317 }
318 
canonicalizeDefines(PreprocessorOptions & PPOpts)319 static void canonicalizeDefines(PreprocessorOptions &PPOpts) {
320   using MacroOpt = std::pair<StringRef, std::size_t>;
321   std::vector<MacroOpt> SimpleNames;
322   SimpleNames.reserve(PPOpts.Macros.size());
323   std::size_t Index = 0;
324   for (const auto &M : PPOpts.Macros) {
325     auto SName = getSimpleMacroName(M.first);
326     // Skip optimizing if we can't guarantee we can preserve relative order.
327     if (!SName)
328       return;
329     SimpleNames.emplace_back(*SName, Index);
330     ++Index;
331   }
332 
333   llvm::stable_sort(SimpleNames, llvm::less_first());
334   // Keep the last instance of each macro name by going in reverse
335   auto NewEnd = std::unique(
336       SimpleNames.rbegin(), SimpleNames.rend(),
337       [](const MacroOpt &A, const MacroOpt &B) { return A.first == B.first; });
338   SimpleNames.erase(SimpleNames.begin(), NewEnd.base());
339 
340   // Apply permutation.
341   decltype(PPOpts.Macros) NewMacros;
342   NewMacros.reserve(SimpleNames.size());
343   for (std::size_t I = 0, E = SimpleNames.size(); I != E; ++I) {
344     std::size_t OriginalIndex = SimpleNames[I].second;
345     // We still emit undefines here as they may be undefining a predefined macro
346     NewMacros.push_back(std::move(PPOpts.Macros[OriginalIndex]));
347   }
348   std::swap(PPOpts.Macros, NewMacros);
349 }
350 
351 class ScanningDependencyDirectivesGetter : public DependencyDirectivesGetter {
352   DependencyScanningWorkerFilesystem *DepFS;
353 
354 public:
ScanningDependencyDirectivesGetter(FileManager & FileMgr)355   ScanningDependencyDirectivesGetter(FileManager &FileMgr) : DepFS(nullptr) {
356     FileMgr.getVirtualFileSystem().visit([&](llvm::vfs::FileSystem &FS) {
357       auto *DFS = llvm::dyn_cast<DependencyScanningWorkerFilesystem>(&FS);
358       if (DFS) {
359         assert(!DepFS && "Found multiple scanning VFSs");
360         DepFS = DFS;
361       }
362     });
363     assert(DepFS && "Did not find scanning VFS");
364   }
365 
366   std::unique_ptr<DependencyDirectivesGetter>
cloneFor(FileManager & FileMgr)367   cloneFor(FileManager &FileMgr) override {
368     return std::make_unique<ScanningDependencyDirectivesGetter>(FileMgr);
369   }
370 
371   std::optional<ArrayRef<dependency_directives_scan::Directive>>
operator ()(FileEntryRef File)372   operator()(FileEntryRef File) override {
373     return DepFS->getDirectiveTokens(File.getName());
374   }
375 };
376 
377 /// A clang tool that runs the preprocessor in a mode that's optimized for
378 /// dependency scanning for the given compiler invocation.
379 class DependencyScanningAction : public tooling::ToolAction {
380 public:
DependencyScanningAction(DependencyScanningService & Service,StringRef WorkingDirectory,DependencyConsumer & Consumer,DependencyActionController & Controller,llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS,bool DisableFree,std::optional<StringRef> ModuleName=std::nullopt)381   DependencyScanningAction(
382       DependencyScanningService &Service, StringRef WorkingDirectory,
383       DependencyConsumer &Consumer, DependencyActionController &Controller,
384       llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS,
385       bool DisableFree, std::optional<StringRef> ModuleName = std::nullopt)
386       : Service(Service), WorkingDirectory(WorkingDirectory),
387         Consumer(Consumer), Controller(Controller), DepFS(std::move(DepFS)),
388         DisableFree(DisableFree), ModuleName(ModuleName) {}
389 
runInvocation(std::shared_ptr<CompilerInvocation> Invocation,FileManager * DriverFileMgr,std::shared_ptr<PCHContainerOperations> PCHContainerOps,DiagnosticConsumer * DiagConsumer)390   bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
391                      FileManager *DriverFileMgr,
392                      std::shared_ptr<PCHContainerOperations> PCHContainerOps,
393                      DiagnosticConsumer *DiagConsumer) override {
394     // Make a deep copy of the original Clang invocation.
395     CompilerInvocation OriginalInvocation(*Invocation);
396     // Restore the value of DisableFree, which may be modified by Tooling.
397     OriginalInvocation.getFrontendOpts().DisableFree = DisableFree;
398     if (any(Service.getOptimizeArgs() & ScanningOptimizations::Macros))
399       canonicalizeDefines(OriginalInvocation.getPreprocessorOpts());
400 
401     if (Scanned) {
402       // Scanning runs once for the first -cc1 invocation in a chain of driver
403       // jobs. For any dependent jobs, reuse the scanning result and just
404       // update the LastCC1Arguments to correspond to the new invocation.
405       // FIXME: to support multi-arch builds, each arch requires a separate scan
406       setLastCC1Arguments(std::move(OriginalInvocation));
407       return true;
408     }
409 
410     Scanned = true;
411 
412     // Create a compiler instance to handle the actual work.
413     auto ModCache = makeInProcessModuleCache(Service.getModuleCacheEntries());
414     ScanInstanceStorage.emplace(std::move(Invocation),
415                                 std::move(PCHContainerOps), ModCache.get());
416     CompilerInstance &ScanInstance = *ScanInstanceStorage;
417     ScanInstance.setBuildingModule(false);
418 
419     // Create the compiler's actual diagnostics engine.
420     sanitizeDiagOpts(ScanInstance.getDiagnosticOpts());
421     assert(!DiagConsumerFinished && "attempt to reuse finished consumer");
422     ScanInstance.createDiagnostics(DriverFileMgr->getVirtualFileSystem(),
423                                    DiagConsumer, /*ShouldOwnClient=*/false);
424     if (!ScanInstance.hasDiagnostics())
425       return false;
426 
427     ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath =
428         true;
429 
430     if (ScanInstance.getHeaderSearchOpts().ModulesValidateOncePerBuildSession)
431       ScanInstance.getHeaderSearchOpts().BuildSessionTimestamp =
432           Service.getBuildSessionTimestamp();
433 
434     ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false;
435     ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false;
436     // This will prevent us compiling individual modules asynchronously since
437     // FileManager is not thread-safe, but it does improve performance for now.
438     ScanInstance.getFrontendOpts().ModulesShareFileManager = true;
439     ScanInstance.getHeaderSearchOpts().ModuleFormat = "raw";
440     ScanInstance.getHeaderSearchOpts().ModulesIncludeVFSUsage =
441         any(Service.getOptimizeArgs() & ScanningOptimizations::VFS);
442 
443     // Support for virtual file system overlays.
444     auto FS = createVFSFromCompilerInvocation(
445         ScanInstance.getInvocation(), ScanInstance.getDiagnostics(),
446         DriverFileMgr->getVirtualFileSystemPtr());
447 
448     // Create a new FileManager to match the invocation's FileSystemOptions.
449     auto *FileMgr = ScanInstance.createFileManager(FS);
450 
451     // Use the dependency scanning optimized file system if requested to do so.
452     if (DepFS) {
453       StringRef ModulesCachePath =
454           ScanInstance.getHeaderSearchOpts().ModuleCachePath;
455 
456       DepFS->resetBypassedPathPrefix();
457       if (!ModulesCachePath.empty())
458         DepFS->setBypassedPathPrefix(ModulesCachePath);
459 
460       ScanInstance.setDependencyDirectivesGetter(
461           std::make_unique<ScanningDependencyDirectivesGetter>(*FileMgr));
462     }
463 
464     ScanInstance.createSourceManager(*FileMgr);
465 
466     // Create a collection of stable directories derived from the ScanInstance
467     // for determining whether module dependencies would fully resolve from
468     // those directories.
469     llvm::SmallVector<StringRef> StableDirs;
470     const StringRef Sysroot = ScanInstance.getHeaderSearchOpts().Sysroot;
471     if (!Sysroot.empty() &&
472         (llvm::sys::path::root_directory(Sysroot) != Sysroot))
473       StableDirs = {Sysroot, ScanInstance.getHeaderSearchOpts().ResourceDir};
474 
475     // Store a mapping of prebuilt module files and their properties like header
476     // search options. This will prevent the implicit build to create duplicate
477     // modules and will force reuse of the existing prebuilt module files
478     // instead.
479     PrebuiltModulesAttrsMap PrebuiltModulesASTMap;
480 
481     if (!ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty())
482       if (visitPrebuiltModule(
483               ScanInstance.getPreprocessorOpts().ImplicitPCHInclude,
484               ScanInstance,
485               ScanInstance.getHeaderSearchOpts().PrebuiltModuleFiles,
486               PrebuiltModulesASTMap, ScanInstance.getDiagnostics(), StableDirs))
487         return false;
488 
489     // Create the dependency collector that will collect the produced
490     // dependencies.
491     //
492     // This also moves the existing dependency output options from the
493     // invocation to the collector. The options in the invocation are reset,
494     // which ensures that the compiler won't create new dependency collectors,
495     // and thus won't write out the extra '.d' files to disk.
496     auto Opts = std::make_unique<DependencyOutputOptions>();
497     std::swap(*Opts, ScanInstance.getInvocation().getDependencyOutputOpts());
498     // We need at least one -MT equivalent for the generator of make dependency
499     // files to work.
500     if (Opts->Targets.empty())
501       Opts->Targets = {
502           deduceDepTarget(ScanInstance.getFrontendOpts().OutputFile,
503                           ScanInstance.getFrontendOpts().Inputs)};
504     Opts->IncludeSystemHeaders = true;
505 
506     switch (Service.getFormat()) {
507     case ScanningOutputFormat::Make:
508       ScanInstance.addDependencyCollector(
509           std::make_shared<DependencyConsumerForwarder>(
510               std::move(Opts), WorkingDirectory, Consumer));
511       break;
512     case ScanningOutputFormat::P1689:
513     case ScanningOutputFormat::Full:
514       MDC = std::make_shared<ModuleDepCollector>(
515           Service, std::move(Opts), ScanInstance, Consumer, Controller,
516           OriginalInvocation, std::move(PrebuiltModulesASTMap), StableDirs);
517       ScanInstance.addDependencyCollector(MDC);
518       break;
519     }
520 
521     // Consider different header search and diagnostic options to create
522     // different modules. This avoids the unsound aliasing of module PCMs.
523     //
524     // TODO: Implement diagnostic bucketing to reduce the impact of strict
525     // context hashing.
526     ScanInstance.getHeaderSearchOpts().ModulesStrictContextHash = true;
527     ScanInstance.getHeaderSearchOpts().ModulesSerializeOnlyPreprocessor = true;
528     ScanInstance.getHeaderSearchOpts().ModulesSkipDiagnosticOptions = true;
529     ScanInstance.getHeaderSearchOpts().ModulesSkipHeaderSearchPaths = true;
530     ScanInstance.getHeaderSearchOpts().ModulesSkipPragmaDiagnosticMappings =
531         true;
532     ScanInstance.getHeaderSearchOpts().ModulesForceValidateUserHeaders = false;
533 
534     // Avoid some checks and module map parsing when loading PCM files.
535     ScanInstance.getPreprocessorOpts().ModulesCheckRelocated = false;
536 
537     std::unique_ptr<FrontendAction> Action;
538 
539     if (Service.getFormat() == ScanningOutputFormat::P1689)
540       Action = std::make_unique<PreprocessOnlyAction>();
541     else if (ModuleName)
542       Action = std::make_unique<GetDependenciesByModuleNameAction>(*ModuleName);
543     else
544       Action = std::make_unique<ReadPCHAndPreprocessAction>();
545 
546     if (ScanInstance.getDiagnostics().hasErrorOccurred())
547       return false;
548 
549     const bool Result = ScanInstance.ExecuteAction(*Action);
550 
551     // ExecuteAction is responsible for calling finish.
552     DiagConsumerFinished = true;
553 
554     if (Result)
555       setLastCC1Arguments(std::move(OriginalInvocation));
556 
557     // Propagate the statistics to the parent FileManager.
558     DriverFileMgr->AddStats(ScanInstance.getFileManager());
559 
560     return Result;
561   }
562 
hasScanned() const563   bool hasScanned() const { return Scanned; }
hasDiagConsumerFinished() const564   bool hasDiagConsumerFinished() const { return DiagConsumerFinished; }
565 
566   /// Take the cc1 arguments corresponding to the most recent invocation used
567   /// with this action. Any modifications implied by the discovered dependencies
568   /// will have already been applied.
takeLastCC1Arguments()569   std::vector<std::string> takeLastCC1Arguments() {
570     std::vector<std::string> Result;
571     std::swap(Result, LastCC1Arguments); // Reset LastCC1Arguments to empty.
572     return Result;
573   }
574 
575 private:
setLastCC1Arguments(CompilerInvocation && CI)576   void setLastCC1Arguments(CompilerInvocation &&CI) {
577     if (MDC)
578       MDC->applyDiscoveredDependencies(CI);
579     LastCC1Arguments = CI.getCC1CommandLine();
580   }
581 
582   DependencyScanningService &Service;
583   StringRef WorkingDirectory;
584   DependencyConsumer &Consumer;
585   DependencyActionController &Controller;
586   llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS;
587   bool DisableFree;
588   std::optional<StringRef> ModuleName;
589   std::optional<CompilerInstance> ScanInstanceStorage;
590   std::shared_ptr<ModuleDepCollector> MDC;
591   std::vector<std::string> LastCC1Arguments;
592   bool Scanned = false;
593   bool DiagConsumerFinished = false;
594 };
595 
596 } // end anonymous namespace
597 
DependencyScanningWorker(DependencyScanningService & Service,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)598 DependencyScanningWorker::DependencyScanningWorker(
599     DependencyScanningService &Service,
600     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
601     : Service(Service) {
602   PCHContainerOps = std::make_shared<PCHContainerOperations>();
603   // We need to read object files from PCH built outside the scanner.
604   PCHContainerOps->registerReader(
605       std::make_unique<ObjectFilePCHContainerReader>());
606   // The scanner itself writes only raw ast files.
607   PCHContainerOps->registerWriter(std::make_unique<RawPCHContainerWriter>());
608 
609   if (Service.shouldTraceVFS())
610     FS = llvm::makeIntrusiveRefCnt<llvm::vfs::TracingFileSystem>(std::move(FS));
611 
612   switch (Service.getMode()) {
613   case ScanningMode::DependencyDirectivesScan:
614     DepFS =
615         new DependencyScanningWorkerFilesystem(Service.getSharedCache(), FS);
616     BaseFS = DepFS;
617     break;
618   case ScanningMode::CanonicalPreprocessing:
619     DepFS = nullptr;
620     BaseFS = FS;
621     break;
622   }
623 }
624 
625 static std::unique_ptr<DiagnosticOptions>
createDiagOptions(const std::vector<std::string> & CommandLine)626 createDiagOptions(const std::vector<std::string> &CommandLine) {
627   std::vector<const char *> CLI;
628   for (const std::string &Arg : CommandLine)
629     CLI.push_back(Arg.c_str());
630   auto DiagOpts = CreateAndPopulateDiagOpts(CLI);
631   sanitizeDiagOpts(*DiagOpts);
632   return DiagOpts;
633 }
634 
computeDependencies(StringRef WorkingDirectory,const std::vector<std::string> & CommandLine,DependencyConsumer & Consumer,DependencyActionController & Controller,std::optional<llvm::MemoryBufferRef> TUBuffer)635 llvm::Error DependencyScanningWorker::computeDependencies(
636     StringRef WorkingDirectory, const std::vector<std::string> &CommandLine,
637     DependencyConsumer &Consumer, DependencyActionController &Controller,
638     std::optional<llvm::MemoryBufferRef> TUBuffer) {
639   // Capture the emitted diagnostics and report them to the client
640   // in the case of a failure.
641   std::string DiagnosticOutput;
642   llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput);
643   auto DiagOpts = createDiagOptions(CommandLine);
644   TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, *DiagOpts);
645 
646   if (computeDependencies(WorkingDirectory, CommandLine, Consumer, Controller,
647                           DiagPrinter, TUBuffer))
648     return llvm::Error::success();
649   return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(),
650                                              llvm::inconvertibleErrorCode());
651 }
652 
computeDependencies(StringRef WorkingDirectory,const std::vector<std::string> & CommandLine,DependencyConsumer & Consumer,DependencyActionController & Controller,StringRef ModuleName)653 llvm::Error DependencyScanningWorker::computeDependencies(
654     StringRef WorkingDirectory, const std::vector<std::string> &CommandLine,
655     DependencyConsumer &Consumer, DependencyActionController &Controller,
656     StringRef ModuleName) {
657   // Capture the emitted diagnostics and report them to the client
658   // in the case of a failure.
659   std::string DiagnosticOutput;
660   llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput);
661   auto DiagOpts = createDiagOptions(CommandLine);
662   TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, *DiagOpts);
663 
664   if (computeDependencies(WorkingDirectory, CommandLine, Consumer, Controller,
665                           DiagPrinter, ModuleName))
666     return llvm::Error::success();
667   return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(),
668                                              llvm::inconvertibleErrorCode());
669 }
670 
forEachDriverJob(ArrayRef<std::string> ArgStrs,DiagnosticsEngine & Diags,FileManager & FM,llvm::function_ref<bool (const driver::Command & Cmd)> Callback)671 static bool forEachDriverJob(
672     ArrayRef<std::string> ArgStrs, DiagnosticsEngine &Diags, FileManager &FM,
673     llvm::function_ref<bool(const driver::Command &Cmd)> Callback) {
674   SmallVector<const char *, 256> Argv;
675   Argv.reserve(ArgStrs.size());
676   for (const std::string &Arg : ArgStrs)
677     Argv.push_back(Arg.c_str());
678 
679   llvm::vfs::FileSystem *FS = &FM.getVirtualFileSystem();
680 
681   std::unique_ptr<driver::Driver> Driver = std::make_unique<driver::Driver>(
682       Argv[0], llvm::sys::getDefaultTargetTriple(), Diags,
683       "clang LLVM compiler", FS);
684   Driver->setTitle("clang_based_tool");
685 
686   llvm::BumpPtrAllocator Alloc;
687   bool CLMode = driver::IsClangCL(
688       driver::getDriverMode(Argv[0], ArrayRef(Argv).slice(1)));
689 
690   if (llvm::Error E = driver::expandResponseFiles(Argv, CLMode, Alloc, FS)) {
691     Diags.Report(diag::err_drv_expand_response_file)
692         << llvm::toString(std::move(E));
693     return false;
694   }
695 
696   const std::unique_ptr<driver::Compilation> Compilation(
697       Driver->BuildCompilation(llvm::ArrayRef(Argv)));
698   if (!Compilation)
699     return false;
700 
701   if (Compilation->containsError())
702     return false;
703 
704   for (const driver::Command &Job : Compilation->getJobs()) {
705     if (!Callback(Job))
706       return false;
707   }
708   return true;
709 }
710 
createAndRunToolInvocation(std::vector<std::string> CommandLine,DependencyScanningAction & Action,FileManager & FM,std::shared_ptr<clang::PCHContainerOperations> & PCHContainerOps,DiagnosticsEngine & Diags,DependencyConsumer & Consumer)711 static bool createAndRunToolInvocation(
712     std::vector<std::string> CommandLine, DependencyScanningAction &Action,
713     FileManager &FM,
714     std::shared_ptr<clang::PCHContainerOperations> &PCHContainerOps,
715     DiagnosticsEngine &Diags, DependencyConsumer &Consumer) {
716 
717   // Save executable path before providing CommandLine to ToolInvocation
718   std::string Executable = CommandLine[0];
719   ToolInvocation Invocation(std::move(CommandLine), &Action, &FM,
720                             PCHContainerOps);
721   Invocation.setDiagnosticConsumer(Diags.getClient());
722   Invocation.setDiagnosticOptions(&Diags.getDiagnosticOptions());
723   if (!Invocation.run())
724     return false;
725 
726   std::vector<std::string> Args = Action.takeLastCC1Arguments();
727   Consumer.handleBuildCommand({std::move(Executable), std::move(Args)});
728   return true;
729 }
730 
scanDependencies(StringRef WorkingDirectory,const std::vector<std::string> & CommandLine,DependencyConsumer & Consumer,DependencyActionController & Controller,DiagnosticConsumer & DC,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,std::optional<StringRef> ModuleName)731 bool DependencyScanningWorker::scanDependencies(
732     StringRef WorkingDirectory, const std::vector<std::string> &CommandLine,
733     DependencyConsumer &Consumer, DependencyActionController &Controller,
734     DiagnosticConsumer &DC, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
735     std::optional<StringRef> ModuleName) {
736   auto FileMgr =
737       llvm::makeIntrusiveRefCnt<FileManager>(FileSystemOptions{}, FS);
738 
739   std::vector<const char *> CCommandLine(CommandLine.size(), nullptr);
740   llvm::transform(CommandLine, CCommandLine.begin(),
741                   [](const std::string &Str) { return Str.c_str(); });
742   auto DiagOpts = CreateAndPopulateDiagOpts(CCommandLine);
743   sanitizeDiagOpts(*DiagOpts);
744   IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
745       CompilerInstance::createDiagnostics(FileMgr->getVirtualFileSystem(),
746                                           *DiagOpts, &DC,
747                                           /*ShouldOwnClient=*/false);
748 
749   // Although `Diagnostics` are used only for command-line parsing, the
750   // custom `DiagConsumer` might expect a `SourceManager` to be present.
751   SourceManager SrcMgr(*Diags, *FileMgr);
752   Diags->setSourceManager(&SrcMgr);
753   // DisableFree is modified by Tooling for running
754   // in-process; preserve the original value, which is
755   // always true for a driver invocation.
756   bool DisableFree = true;
757   DependencyScanningAction Action(Service, WorkingDirectory, Consumer,
758                                   Controller, DepFS, DisableFree, ModuleName);
759 
760   bool Success = false;
761   if (CommandLine[1] == "-cc1") {
762     Success = createAndRunToolInvocation(CommandLine, Action, *FileMgr,
763                                          PCHContainerOps, *Diags, Consumer);
764   } else {
765     Success = forEachDriverJob(
766         CommandLine, *Diags, *FileMgr, [&](const driver::Command &Cmd) {
767           if (StringRef(Cmd.getCreator().getName()) != "clang") {
768             // Non-clang command. Just pass through to the dependency
769             // consumer.
770             Consumer.handleBuildCommand(
771                 {Cmd.getExecutable(),
772                  {Cmd.getArguments().begin(), Cmd.getArguments().end()}});
773             return true;
774           }
775 
776           // Insert -cc1 comand line options into Argv
777           std::vector<std::string> Argv;
778           Argv.push_back(Cmd.getExecutable());
779           llvm::append_range(Argv, Cmd.getArguments());
780 
781           // Create an invocation that uses the underlying file
782           // system to ensure that any file system requests that
783           // are made by the driver do not go through the
784           // dependency scanning filesystem.
785           return createAndRunToolInvocation(std::move(Argv), Action, *FileMgr,
786                                             PCHContainerOps, *Diags, Consumer);
787         });
788   }
789 
790   if (Success && !Action.hasScanned())
791     Diags->Report(diag::err_fe_expected_compiler_job)
792         << llvm::join(CommandLine, " ");
793 
794   // Ensure finish() is called even if we never reached ExecuteAction().
795   if (!Action.hasDiagConsumerFinished())
796     DC.finish();
797 
798   return Success && Action.hasScanned();
799 }
800 
computeDependencies(StringRef WorkingDirectory,const std::vector<std::string> & CommandLine,DependencyConsumer & Consumer,DependencyActionController & Controller,DiagnosticConsumer & DC,std::optional<llvm::MemoryBufferRef> TUBuffer)801 bool DependencyScanningWorker::computeDependencies(
802     StringRef WorkingDirectory, const std::vector<std::string> &CommandLine,
803     DependencyConsumer &Consumer, DependencyActionController &Controller,
804     DiagnosticConsumer &DC, std::optional<llvm::MemoryBufferRef> TUBuffer) {
805   // Reset what might have been modified in the previous worker invocation.
806   BaseFS->setCurrentWorkingDirectory(WorkingDirectory);
807 
808   std::optional<std::vector<std::string>> ModifiedCommandLine;
809   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> ModifiedFS;
810 
811   // If we're scanning based on a module name alone, we don't expect the client
812   // to provide us with an input file. However, the driver really wants to have
813   // one. Let's just make it up to make the driver happy.
814   if (TUBuffer) {
815     auto OverlayFS =
816         llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(BaseFS);
817     auto InMemoryFS =
818         llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
819     InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory);
820     auto InputPath = TUBuffer->getBufferIdentifier();
821     InMemoryFS->addFile(
822         InputPath, 0,
823         llvm::MemoryBuffer::getMemBufferCopy(TUBuffer->getBuffer()));
824     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> InMemoryOverlay =
825         InMemoryFS;
826 
827     OverlayFS->pushOverlay(InMemoryOverlay);
828     ModifiedFS = OverlayFS;
829     ModifiedCommandLine = CommandLine;
830     ModifiedCommandLine->emplace_back(InputPath);
831   }
832 
833   const std::vector<std::string> &FinalCommandLine =
834       ModifiedCommandLine ? *ModifiedCommandLine : CommandLine;
835   auto &FinalFS = ModifiedFS ? ModifiedFS : BaseFS;
836 
837   return scanDependencies(WorkingDirectory, FinalCommandLine, Consumer,
838                           Controller, DC, FinalFS, /*ModuleName=*/std::nullopt);
839 }
840 
computeDependencies(StringRef WorkingDirectory,const std::vector<std::string> & CommandLine,DependencyConsumer & Consumer,DependencyActionController & Controller,DiagnosticConsumer & DC,StringRef ModuleName)841 bool DependencyScanningWorker::computeDependencies(
842     StringRef WorkingDirectory, const std::vector<std::string> &CommandLine,
843     DependencyConsumer &Consumer, DependencyActionController &Controller,
844     DiagnosticConsumer &DC, StringRef ModuleName) {
845   // Reset what might have been modified in the previous worker invocation.
846   BaseFS->setCurrentWorkingDirectory(WorkingDirectory);
847 
848   // If we're scanning based on a module name alone, we don't expect the client
849   // to provide us with an input file. However, the driver really wants to have
850   // one. Let's just make it up to make the driver happy.
851   auto OverlayFS =
852       llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(BaseFS);
853   auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
854   InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory);
855   SmallString<128> FakeInputPath;
856   // TODO: We should retry the creation if the path already exists.
857   llvm::sys::fs::createUniquePath(ModuleName + "-%%%%%%%%.input", FakeInputPath,
858                                   /*MakeAbsolute=*/false);
859   InMemoryFS->addFile(FakeInputPath, 0, llvm::MemoryBuffer::getMemBuffer(""));
860   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> InMemoryOverlay = InMemoryFS;
861 
862   OverlayFS->pushOverlay(InMemoryOverlay);
863   auto ModifiedCommandLine = CommandLine;
864   ModifiedCommandLine.emplace_back(FakeInputPath);
865 
866   return scanDependencies(WorkingDirectory, ModifiedCommandLine, Consumer,
867                           Controller, DC, OverlayFS, ModuleName);
868 }
869 
~DependencyActionController()870 DependencyActionController::~DependencyActionController() {}
871