Skip to content

Commit de3b2c2

Browse files
authored
[clang][ScanDeps] Allow PCHs to have different VFS overlays (llvm#82294)
It turns out it's not that uncommon for real code to pass a different set of VFSs while building a PCH than while using the PCH. This can cause problems as seen in `test/ClangScanDeps/optimize-vfs-pch.m`. If you scan `compile-commands-tu-no-vfs-error.json` without -Werror and run the resulting commands, Clang will emit a fatal error while trying to emit a note saying that it can't find a remapped header. This also adds textual tracking of VFSs for prebuilt modules that are part of an included PCH, as the same issue can occur in a module we are building if we drop VFSs. This has to be textual because we have no guarantee the PCH had the same list of VFSs as the current TU. This uses the `PrebuiltModuleListener` to collect `VFSOverlayFiles` instead of trying to extract it out of a `serialization::ModuleFile` each time it's needed. There's not a great way to just store a pointer to the list of strings in the serialized AST.
1 parent d42de86 commit de3b2c2

File tree

6 files changed

+190
-29
lines changed

6 files changed

+190
-29
lines changed

clang/include/clang/Basic/DiagnosticSerializationKinds.td

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ def err_pch_diagopt_mismatch : Error<"%0 is currently enabled, but was not in "
4444
"the PCH file">;
4545
def err_pch_modulecache_mismatch : Error<"PCH was compiled with module cache "
4646
"path '%0', but the path is currently '%1'">;
47-
def err_pch_vfsoverlay_mismatch : Error<"PCH was compiled with different VFS overlay files than are currently in use">;
47+
def warn_pch_vfsoverlay_mismatch : Warning<
48+
"PCH was compiled with different VFS overlay files than are currently in use">,
49+
InGroup<DiagGroup<"pch-vfs-diff">>;
4850
def note_pch_vfsoverlay_files : Note<"%select{PCH|current translation unit}0 has the following VFS overlays:\n%1">;
4951
def note_pch_vfsoverlay_empty : Note<"%select{PCH|current translation unit}0 has no VFS overlays">;
5052

clang/include/clang/Tooling/DependencyScanning/ModuleDepCollector.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ struct ModuleDeps {
149149
BuildInfo;
150150
};
151151

152+
using PrebuiltModuleVFSMapT = llvm::StringMap<llvm::StringSet<>>;
153+
152154
class ModuleDepCollector;
153155

154156
/// Callback that records textual includes and direct modular includes/imports
@@ -214,6 +216,7 @@ class ModuleDepCollector final : public DependencyCollector {
214216
CompilerInstance &ScanInstance, DependencyConsumer &C,
215217
DependencyActionController &Controller,
216218
CompilerInvocation OriginalCI,
219+
PrebuiltModuleVFSMapT PrebuiltModuleVFSMap,
217220
ScanningOptimizations OptimizeArgs, bool EagerLoadModules,
218221
bool IsStdModuleP1689Format);
219222

@@ -233,6 +236,8 @@ class ModuleDepCollector final : public DependencyCollector {
233236
DependencyConsumer &Consumer;
234237
/// Callbacks for computing dependency information.
235238
DependencyActionController &Controller;
239+
/// Mapping from prebuilt AST files to their sorted list of VFS overlay files.
240+
PrebuiltModuleVFSMapT PrebuiltModuleVFSMap;
236241
/// Path to the main source file.
237242
std::string MainFile;
238243
/// Hash identifying the compilation conditions of the current TU.

clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
2525
#include "clang/Tooling/DependencyScanning/ModuleDepCollector.h"
2626
#include "clang/Tooling/Tooling.h"
27+
#include "llvm/ADT/ScopeExit.h"
2728
#include "llvm/Support/Allocator.h"
2829
#include "llvm/Support/Error.h"
2930
#include "llvm/TargetParser/Host.h"
@@ -67,7 +68,7 @@ static bool checkHeaderSearchPaths(const HeaderSearchOptions &HSOpts,
6768
if (LangOpts.Modules) {
6869
if (HSOpts.VFSOverlayFiles != ExistingHSOpts.VFSOverlayFiles) {
6970
if (Diags) {
70-
Diags->Report(diag::err_pch_vfsoverlay_mismatch);
71+
Diags->Report(diag::warn_pch_vfsoverlay_mismatch);
7172
auto VFSNote = [&](int Type, ArrayRef<std::string> VFSOverlays) {
7273
if (VFSOverlays.empty()) {
7374
Diags->Report(diag::note_pch_vfsoverlay_empty) << Type;
@@ -79,7 +80,6 @@ static bool checkHeaderSearchPaths(const HeaderSearchOptions &HSOpts,
7980
VFSNote(0, HSOpts.VFSOverlayFiles);
8081
VFSNote(1, ExistingHSOpts.VFSOverlayFiles);
8182
}
82-
return true;
8383
}
8484
}
8585
return false;
@@ -93,10 +93,12 @@ class PrebuiltModuleListener : public ASTReaderListener {
9393
public:
9494
PrebuiltModuleListener(PrebuiltModuleFilesT &PrebuiltModuleFiles,
9595
llvm::SmallVector<std::string> &NewModuleFiles,
96+
PrebuiltModuleVFSMapT &PrebuiltModuleVFSMap,
9697
const HeaderSearchOptions &HSOpts,
9798
const LangOptions &LangOpts, DiagnosticsEngine &Diags)
9899
: PrebuiltModuleFiles(PrebuiltModuleFiles),
99-
NewModuleFiles(NewModuleFiles), ExistingHSOpts(HSOpts),
100+
NewModuleFiles(NewModuleFiles),
101+
PrebuiltModuleVFSMap(PrebuiltModuleVFSMap), ExistingHSOpts(HSOpts),
100102
ExistingLangOpts(LangOpts), Diags(Diags) {}
101103

102104
bool needsImportVisitation() const override { return true; }
@@ -106,31 +108,45 @@ class PrebuiltModuleListener : public ASTReaderListener {
106108
NewModuleFiles.push_back(Filename.str());
107109
}
108110

111+
void visitModuleFile(StringRef Filename,
112+
serialization::ModuleKind Kind) override {
113+
CurrentFile = Filename;
114+
}
115+
109116
bool ReadHeaderSearchPaths(const HeaderSearchOptions &HSOpts,
110117
bool Complain) override {
118+
std::vector<std::string> VFSOverlayFiles = HSOpts.VFSOverlayFiles;
119+
PrebuiltModuleVFSMap.insert(
120+
{CurrentFile, llvm::StringSet<>(VFSOverlayFiles)});
111121
return checkHeaderSearchPaths(
112122
HSOpts, ExistingHSOpts, Complain ? &Diags : nullptr, ExistingLangOpts);
113123
}
114124

115125
private:
116126
PrebuiltModuleFilesT &PrebuiltModuleFiles;
117127
llvm::SmallVector<std::string> &NewModuleFiles;
128+
PrebuiltModuleVFSMapT &PrebuiltModuleVFSMap;
118129
const HeaderSearchOptions &ExistingHSOpts;
119130
const LangOptions &ExistingLangOpts;
120131
DiagnosticsEngine &Diags;
132+
std::string CurrentFile;
121133
};
122134

123135
/// Visit the given prebuilt module and collect all of the modules it
124136
/// transitively imports and contributing input files.
125137
static bool visitPrebuiltModule(StringRef PrebuiltModuleFilename,
126138
CompilerInstance &CI,
127139
PrebuiltModuleFilesT &ModuleFiles,
140+
PrebuiltModuleVFSMapT &PrebuiltModuleVFSMap,
128141
DiagnosticsEngine &Diags) {
129142
// List of module files to be processed.
130143
llvm::SmallVector<std::string> Worklist;
131-
PrebuiltModuleListener Listener(
132-
ModuleFiles, Worklist, CI.getHeaderSearchOpts(), CI.getLangOpts(), Diags);
144+
PrebuiltModuleListener Listener(ModuleFiles, Worklist, PrebuiltModuleVFSMap,
145+
CI.getHeaderSearchOpts(), CI.getLangOpts(),
146+
Diags);
133147

148+
Listener.visitModuleFile(PrebuiltModuleFilename,
149+
serialization::MK_ExplicitModule);
134150
if (ASTReader::readASTFileControlBlock(
135151
PrebuiltModuleFilename, CI.getFileManager(), CI.getModuleCache(),
136152
CI.getPCHContainerReader(),
@@ -139,6 +155,7 @@ static bool visitPrebuiltModule(StringRef PrebuiltModuleFilename,
139155
return true;
140156

141157
while (!Worklist.empty()) {
158+
Listener.visitModuleFile(Worklist.back(), serialization::MK_ExplicitModule);
142159
if (ASTReader::readASTFileControlBlock(
143160
Worklist.pop_back_val(), CI.getFileManager(), CI.getModuleCache(),
144161
CI.getPCHContainerReader(),
@@ -175,8 +192,19 @@ static void sanitizeDiagOpts(DiagnosticOptions &DiagOpts) {
175192
DiagOpts.ShowCarets = false;
176193
// Don't write out diagnostic file.
177194
DiagOpts.DiagnosticSerializationFile.clear();
178-
// Don't emit warnings as errors (and all other warnings too).
179-
DiagOpts.IgnoreWarnings = true;
195+
// Don't emit warnings except for scanning specific warnings.
196+
// TODO: It would be useful to add a more principled way to ignore all
197+
// warnings that come from source code. The issue is that we need to
198+
// ignore warnings that could be surpressed by
199+
// `#pragma clang diagnostic`, while still allowing some scanning
200+
// warnings for things we're not ready to turn into errors yet.
201+
// See `test/ClangScanDeps/diagnostic-pragmas.c` for an example.
202+
llvm::erase_if(DiagOpts.Warnings, [](StringRef Warning) {
203+
return llvm::StringSwitch<bool>(Warning)
204+
.Cases("pch-vfs-diff", "error=pch-vfs-diff", false)
205+
.StartsWith("no-error=", false)
206+
.Default(true);
207+
});
180208
}
181209

182210
// Clang implements -D and -U by splatting text into a predefines buffer. This
@@ -300,14 +328,19 @@ class DependencyScanningAction : public tooling::ToolAction {
300328
if (!ScanInstance.hasDiagnostics())
301329
return false;
302330

331+
// Some DiagnosticConsumers require that finish() is called.
332+
auto DiagConsumerFinisher =
333+
llvm::make_scope_exit([DiagConsumer]() { DiagConsumer->finish(); });
334+
303335
ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath =
304336
true;
305337

306338
ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false;
307339
ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false;
308340
ScanInstance.getFrontendOpts().ModulesShareFileManager = false;
309341
ScanInstance.getHeaderSearchOpts().ModuleFormat = "raw";
310-
ScanInstance.getHeaderSearchOpts().ModulesIncludeVFSUsage = true;
342+
ScanInstance.getHeaderSearchOpts().ModulesIncludeVFSUsage =
343+
any(OptimizeArgs & ScanningOptimizations::VFS);
311344

312345
ScanInstance.setFileManager(FileMgr);
313346
// Support for virtual file system overlays.
@@ -320,12 +353,13 @@ class DependencyScanningAction : public tooling::ToolAction {
320353
// Store the list of prebuilt module files into header search options. This
321354
// will prevent the implicit build to create duplicate modules and will
322355
// force reuse of the existing prebuilt module files instead.
356+
PrebuiltModuleVFSMapT PrebuiltModuleVFSMap;
323357
if (!ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty())
324358
if (visitPrebuiltModule(
325359
ScanInstance.getPreprocessorOpts().ImplicitPCHInclude,
326360
ScanInstance,
327361
ScanInstance.getHeaderSearchOpts().PrebuiltModuleFiles,
328-
ScanInstance.getDiagnostics()))
362+
PrebuiltModuleVFSMap, ScanInstance.getDiagnostics()))
329363
return false;
330364

331365
// Use the dependency scanning optimized file system if requested to do so.
@@ -369,8 +403,8 @@ class DependencyScanningAction : public tooling::ToolAction {
369403
case ScanningOutputFormat::Full:
370404
MDC = std::make_shared<ModuleDepCollector>(
371405
std::move(Opts), ScanInstance, Consumer, Controller,
372-
OriginalInvocation, OptimizeArgs, EagerLoadModules,
373-
Format == ScanningOutputFormat::P1689);
406+
OriginalInvocation, std::move(PrebuiltModuleVFSMap), OptimizeArgs,
407+
EagerLoadModules, Format == ScanningOutputFormat::P1689);
374408
ScanInstance.addDependencyCollector(MDC);
375409
break;
376410
}
@@ -399,6 +433,8 @@ class DependencyScanningAction : public tooling::ToolAction {
399433
if (ScanInstance.getDiagnostics().hasErrorOccurred())
400434
return false;
401435

436+
// Each action is responsible for calling finish.
437+
DiagConsumerFinisher.release();
402438
const bool Result = ScanInstance.ExecuteAction(*Action);
403439

404440
if (Result)

clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ const std::vector<std::string> &ModuleDeps::getBuildArguments() {
2929
return std::get<std::vector<std::string>>(BuildInfo);
3030
}
3131

32-
static void optimizeHeaderSearchOpts(HeaderSearchOptions &Opts,
33-
ASTReader &Reader,
34-
const serialization::ModuleFile &MF,
35-
ScanningOptimizations OptimizeArgs) {
32+
static void
33+
optimizeHeaderSearchOpts(HeaderSearchOptions &Opts, ASTReader &Reader,
34+
const serialization::ModuleFile &MF,
35+
const PrebuiltModuleVFSMapT &PrebuiltModuleVFSMap,
36+
ScanningOptimizations OptimizeArgs) {
3637
if (any(OptimizeArgs & ScanningOptimizations::HeaderSearch)) {
3738
// Only preserve search paths that were used during the dependency scan.
3839
std::vector<HeaderSearchOptions::Entry> Entries;
@@ -65,11 +66,25 @@ static void optimizeHeaderSearchOpts(HeaderSearchOptions &Opts,
6566
llvm::DenseSet<const serialization::ModuleFile *> Visited;
6667
std::function<void(const serialization::ModuleFile *)> VisitMF =
6768
[&](const serialization::ModuleFile *MF) {
68-
VFSUsage |= MF->VFSUsage;
6969
Visited.insert(MF);
70-
for (const serialization::ModuleFile *Import : MF->Imports)
71-
if (!Visited.contains(Import))
72-
VisitMF(Import);
70+
if (MF->Kind == serialization::MK_ImplicitModule) {
71+
VFSUsage |= MF->VFSUsage;
72+
// We only need to recurse into implicit modules. Other module types
73+
// will have the correct set of VFSs for anything they depend on.
74+
for (const serialization::ModuleFile *Import : MF->Imports)
75+
if (!Visited.contains(Import))
76+
VisitMF(Import);
77+
} else {
78+
// This is not an implicitly built module, so it may have different
79+
// VFS options. Fall back to a string comparison instead.
80+
auto VFSMap = PrebuiltModuleVFSMap.find(MF->FileName);
81+
if (VFSMap == PrebuiltModuleVFSMap.end())
82+
return;
83+
for (std::size_t I = 0, E = VFSOverlayFiles.size(); I != E; ++I) {
84+
if (VFSMap->second.contains(VFSOverlayFiles[I]))
85+
VFSUsage[I] = true;
86+
}
87+
}
7388
};
7489
VisitMF(&MF);
7590

@@ -596,6 +611,7 @@ ModuleDepCollectorPP::handleTopLevelModule(const Module *M) {
596611
ScanningOptimizations::VFS)))
597612
optimizeHeaderSearchOpts(BuildInvocation.getMutHeaderSearchOpts(),
598613
*MDC.ScanInstance.getASTReader(), *MF,
614+
MDC.PrebuiltModuleVFSMap,
599615
MDC.OptimizeArgs);
600616
if (any(MDC.OptimizeArgs & ScanningOptimizations::SystemWarnings))
601617
optimizeDiagnosticOpts(
@@ -697,9 +713,11 @@ ModuleDepCollector::ModuleDepCollector(
697713
std::unique_ptr<DependencyOutputOptions> Opts,
698714
CompilerInstance &ScanInstance, DependencyConsumer &C,
699715
DependencyActionController &Controller, CompilerInvocation OriginalCI,
716+
PrebuiltModuleVFSMapT PrebuiltModuleVFSMap,
700717
ScanningOptimizations OptimizeArgs, bool EagerLoadModules,
701718
bool IsStdModuleP1689Format)
702719
: ScanInstance(ScanInstance), Consumer(C), Controller(Controller),
720+
PrebuiltModuleVFSMap(std::move(PrebuiltModuleVFSMap)),
703721
Opts(std::move(Opts)),
704722
CommonInvocation(
705723
makeCommonInvocationForModuleBuild(std::move(OriginalCI))),

0 commit comments

Comments
 (0)