Skip to content

Commit 45852f5

Browse files
committed
[clang][ScanDeps] Allow PCHs to have different VFS overlays
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.
1 parent 3f91bdf commit 45852f5

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
/// A clang tool that runs the preprocessor in a mode that's optimized for
@@ -226,14 +254,19 @@ class DependencyScanningAction : public tooling::ToolAction {
226254
if (!ScanInstance.hasDiagnostics())
227255
return false;
228256

257+
// Some DiagnosticConsumers require that finish() is called.
258+
auto DiagConsumerFinisher =
259+
llvm::make_scope_exit([DiagConsumer]() { DiagConsumer->finish(); });
260+
229261
ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath =
230262
true;
231263

232264
ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false;
233265
ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false;
234266
ScanInstance.getFrontendOpts().ModulesShareFileManager = false;
235267
ScanInstance.getHeaderSearchOpts().ModuleFormat = "raw";
236-
ScanInstance.getHeaderSearchOpts().ModulesIncludeVFSUsage = true;
268+
ScanInstance.getHeaderSearchOpts().ModulesIncludeVFSUsage =
269+
any(OptimizeArgs & ScanningOptimizations::VFS);
237270

238271
ScanInstance.setFileManager(FileMgr);
239272
// Support for virtual file system overlays.
@@ -246,12 +279,13 @@ class DependencyScanningAction : public tooling::ToolAction {
246279
// Store the list of prebuilt module files into header search options. This
247280
// will prevent the implicit build to create duplicate modules and will
248281
// force reuse of the existing prebuilt module files instead.
282+
PrebuiltModuleVFSMapT PrebuiltModuleVFSMap;
249283
if (!ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty())
250284
if (visitPrebuiltModule(
251285
ScanInstance.getPreprocessorOpts().ImplicitPCHInclude,
252286
ScanInstance,
253287
ScanInstance.getHeaderSearchOpts().PrebuiltModuleFiles,
254-
ScanInstance.getDiagnostics()))
288+
PrebuiltModuleVFSMap, ScanInstance.getDiagnostics()))
255289
return false;
256290

257291
// Use the dependency scanning optimized file system if requested to do so.
@@ -295,8 +329,8 @@ class DependencyScanningAction : public tooling::ToolAction {
295329
case ScanningOutputFormat::Full:
296330
MDC = std::make_shared<ModuleDepCollector>(
297331
std::move(Opts), ScanInstance, Consumer, Controller,
298-
OriginalInvocation, OptimizeArgs, EagerLoadModules,
299-
Format == ScanningOutputFormat::P1689);
332+
OriginalInvocation, std::move(PrebuiltModuleVFSMap), OptimizeArgs,
333+
EagerLoadModules, Format == ScanningOutputFormat::P1689);
300334
ScanInstance.addDependencyCollector(MDC);
301335
break;
302336
}
@@ -325,6 +359,8 @@ class DependencyScanningAction : public tooling::ToolAction {
325359
if (ScanInstance.getDiagnostics().hasErrorOccurred())
326360
return false;
327361

362+
// Each action is responsible for calling finish.
363+
DiagConsumerFinisher.release();
328364
const bool Result = ScanInstance.ExecuteAction(*Action);
329365

330366
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)