Skip to content

Commit 8aff003

Browse files
authored
[clang][include-tree] Fix spurious dependencies (#8266)
This patch fixes an incorrect assumption that only spurious imports cause both `IncludeTreeBuilder::moduleImport()` and `IncludeTreeBuilder::exitedInclude()` to be called for the same directive. This can also happen for `#include` of a header that belongs to the module being implemented that previously got transitively loaded from a PCM. Instead of trying to guess that a submodule was a spurious import (by checking that it was not imported, is not from module file but its parent is from module file), we just mark it missing from umbrella header when we detect this. This allows us to stop putting the temporary `ModuleImport` onto the stack in one function and later popping and replacing it by `SpuriousImport` in another function. rdar://123253616 Cherry-pick of /pull/8264
1 parent e276a0f commit 8aff003

File tree

6 files changed

+126
-63
lines changed

6 files changed

+126
-63
lines changed

clang/include/clang/Basic/Module.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,10 @@ class alignas(8) Module {
341341
/// Whether this is an inferred submodule (module * { ... }).
342342
unsigned IsInferred : 1;
343343

344+
/// Whether this is an inferred submodule that's missing from the umbrella
345+
/// header.
346+
unsigned IsInferredMissingFromUmbrellaHeader : 1;
347+
344348
/// Whether we should infer submodules for this module based on
345349
/// the headers.
346350
///

clang/lib/Basic/Module.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Module::Module(StringRef Name, SourceLocation DefinitionLoc, Module *Parent,
4242
HasIncompatibleModuleFile(false), IsAvailable(true),
4343
IsFromModuleFile(false), IsFramework(IsFramework), IsExplicit(IsExplicit),
4444
IsSystem(false), IsExternC(false), IsInferred(false),
45+
IsInferredMissingFromUmbrellaHeader(false),
4546
InferSubmodules(false), InferExplicitSubmodules(false),
4647
InferExportWildcard(false), ConfigMacrosExhaustive(false),
4748
NoUndeclaredIncludes(false), ModuleMapIsPrivate(false),

clang/lib/Frontend/CompilerInstance.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2234,6 +2234,8 @@ CompilerInstance::loadModule(SourceLocation ImportLoc,
22342234
<< Module->getFullModuleName()
22352235
<< SourceRange(Path.front().second, Path.back().second);
22362236

2237+
Module->IsInferredMissingFromUmbrellaHeader = true;
2238+
22372239
return ModuleLoadResult(Module, ModuleLoadResult::MissingExpected);
22382240
}
22392241

clang/lib/Tooling/DependencyScanning/IncludeTreeActionController.cpp

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,7 @@ struct IncludeTreePPCallbacks : public PPCallbacks {
204204
bool ModuleImported,
205205
SrcMgr::CharacteristicKind FileType) override {
206206
// File includes are handled by LexedFileChanged.
207-
if (!SuggestedModule)
208-
return;
209-
210-
// Modules that will not be imported and were not loaded from an AST file
211-
// (e.g. the module being implemented) are also handled by LexedFileChanged.
212-
if (!ModuleImported && !SuggestedModule->getASTFile())
207+
if (!ModuleImported)
213208
return;
214209

215210
// Calculate EndLoc for the directive
@@ -457,20 +452,31 @@ void IncludeTreeBuilder::exitedInclude(Preprocessor &PP, FileID IncludedBy,
457452
SourceManager &SM = PP.getSourceManager();
458453
std::pair<FileID, unsigned> LocInfo = SM.getDecomposedExpansionLoc(ExitLoc);
459454

460-
// If the file includes already has a node for the current source location, it
461-
// must be an import that turned out to be spurious.
462-
auto &CurIncludes = IncludeStack.back().Includes;
463-
if (!CurIncludes.empty() && CurIncludes.back().Offset == LocInfo.second) {
464-
assert(!IncludeTree->isSubmodule());
465-
auto Import = CurIncludes.pop_back_val();
466-
assert(Import.Kind == cas::IncludeTree::NodeKind::ModuleImport);
467-
auto SpuriousImport = cas::IncludeTree::SpuriousImport::create(
468-
DB, Import.Ref, IncludeTree->getRef());
469-
if (!SpuriousImport)
455+
// If the exited header belongs to a sub-module that's marked as missing from
456+
// the umbrella, we must've first loaded its PCM file to find that out.
457+
// We need to match this behavior with include-tree. Let's mark this as
458+
// spurious import. For this node, Clang will load the top-level module, emit
459+
// the appropriate diagnostics and then fall back to textual inclusion of the
460+
// header itself.
461+
if (auto FE = PP.getSourceManager().getFileEntryRefForID(Include)) {
462+
ModuleMap &ModMap = PP.getHeaderSearchInfo().getModuleMap();
463+
Module *M = ModMap.findModuleForHeader(*FE).getModule();
464+
if (M && M->IsInferredMissingFromUmbrellaHeader) {
465+
assert(!IncludeTree->isSubmodule() &&
466+
"Include of header missing from umbrella header is modular");
467+
468+
moduleImport(PP, M, ExitLoc);
469+
auto Import = IncludeStack.back().Includes.pop_back_val();
470+
471+
auto SpuriousImport = check(cas::IncludeTree::SpuriousImport::create(
472+
DB, Import.Ref, IncludeTree->getRef()));
473+
if (!SpuriousImport)
474+
return;
475+
IncludeStack.back().Includes.push_back(
476+
{SpuriousImport->getRef(), LocInfo.second,
477+
cas::IncludeTree::NodeKind::SpuriousImport});
470478
return;
471-
CurIncludes.push_back({SpuriousImport->getRef(), LocInfo.second,
472-
cas::IncludeTree::NodeKind::SpuriousImport});
473-
return;
479+
}
474480
}
475481

476482
IncludeStack.back().Includes.push_back({IncludeTree->getRef(), LocInfo.second,
Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
// REQUIRES: ondisk_cas
22

3-
// This test checks that we correctly handle situations where a spurious modular
4-
// dependency (1) turns otherwise textual dependency into modular (2).
5-
//
6-
// (1) For example #include <Spurious/Missing.h>, where the framework Spurious
7-
// has an umbrella header that does not include Missing.h, making it a textual
8-
// include instead.
9-
//
10-
// (2) For example when compiling the implementation file of a module Mod,
11-
// #included headers belonging to Mod are treated textually, unless some other
12-
// module already depends on Mod in its modular form.
3+
// This test checks that imports of transitively-loaded implementation module
4+
// are not marked as spurious.
135

146
// RUN: rm -rf %t
157
// RUN: split-file %s %t
@@ -19,56 +11,40 @@
1911
[{
2012
"file": "DIR/tu.m",
2113
"directory": "DIR",
22-
"command": "clang -c DIR/tu.m -o DIR/tu.o -F DIR/frameworks -I DIR/include -fmodule-name=Mod -fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/module-cache"
14+
"command": "clang -c DIR/tu.m -o DIR/tu.o -F DIR/frameworks -fmodules -fmodule-name=FW -fmodules-cache-path=DIR/module-cache"
2315
}]
2416

25-
//--- frameworks/Spurious.framework/Modules/module.modulemap
26-
framework module Spurious {
27-
umbrella header "Spurious.h"
28-
module * { export * }
29-
}
30-
//--- frameworks/Spurious.framework/Headers/Spurious.h
31-
#include <Mod.h>
32-
//--- frameworks/Spurious.framework/Headers/Missing.h
17+
//--- frameworks/FW.framework/Modules/module.modulemap
18+
framework module FW { umbrella header "FW.h" }
19+
//--- frameworks/FW.framework/Headers/FW.h
20+
#include <FW/Sub.h>
21+
//--- frameworks/FW.framework/Headers/Sub.h
3322

34-
//--- include/module.modulemap
23+
//--- module.modulemap
3524
module Mod { header "Mod.h" }
36-
//--- include/Mod.h
37-
typedef int mod_int;
38-
25+
//--- Mod.h
26+
#include <FW/Sub.h>
3927
//--- tu.m
40-
#include <Spurious/Missing.h>
41-
#include <Mod.h>
42-
static mod_int x;
43-
44-
// RUN: clang-scan-deps -compilation-database %t/cdb.json \
45-
// RUN: -format experimental-full \
46-
// RUN: -module-files-dir %t/outputs > %t/deps.json
47-
48-
// RUN: %deps-to-rsp %t/deps.json --module-name=Mod > %t/Mod.cc1.rsp
49-
// RUN: %deps-to-rsp %t/deps.json --module-name=Spurious > %t/Spurious.cc1.rsp
50-
// RUN: %deps-to-rsp %t/deps.json --tu-index=0 > %t/tu.rsp
51-
52-
// RUN: %clang @%t/Mod.cc1.rsp
53-
// RUN: %clang @%t/Spurious.cc1.rsp
54-
// RUN: %clang @%t/tu.rsp
28+
#include "Mod.h"
29+
#include <FW/Sub.h>
5530

5631
// RUN: clang-scan-deps -compilation-database %t/cdb.json \
5732
// RUN: -format experimental-include-tree-full -cas-path %t/cas \
5833
// RUN: -module-files-dir %t/cas-outputs > %t/cas-deps.json
5934

60-
// RUN: %deps-to-rsp %t/cas-deps.json --module-name=Mod > %t/cas-Mod.cc1.rsp
61-
// RUN: %deps-to-rsp %t/cas-deps.json --module-name=Spurious > %t/cas-Spurious.cc1.rsp
62-
// RUN: %deps-to-rsp %t/cas-deps.json --tu-index=0 > %t/cas-tu.rsp
35+
// RUN: %deps-to-rsp %t/cas-deps.json --module-name=FW > %t/cas-FW.cc1.rsp
36+
// RUN: %deps-to-rsp %t/cas-deps.json --module-name=Mod > %t/cas-Mod.cc1.rsp
37+
// RUN: %deps-to-rsp %t/cas-deps.json --tu-index=0 > %t/cas-tu.rsp
6338

6439
// RUN: cat %t/cas-tu.rsp | sed -E 's|.*"-fcas-include-tree" "(llvmcas://[[:xdigit:]]+)".*|\1|' > %t/tu.casid
6540
// RUN: clang-cas-test -cas %t/cas -print-include-tree @%t/tu.casid > %t/tu-include-tree.txt
6641
// RUN: FileCheck %s -input-file %t/tu-include-tree.txt -DPREFIX=%/t
6742
// CHECK: [[PREFIX]]/tu.m llvmcas://
6843
// CHECK-NEXT: 1:1 <built-in> llvmcas://
69-
// CHECK-NEXT: 2:1 (Spurious import) (Module) Spurious.Missing [[PREFIX]]/frameworks/Spurious.framework/Headers/Missing.h llvmcas://
70-
// CHECK-NEXT: 3:1 (Module for visibility only) Mod
44+
// CHECK-NEXT: 2:1 (Module) Mod
45+
// CHECK-NEXT: 3:1 [[PREFIX]]/frameworks/FW.framework/Headers/Sub.h llvmcas://{{.*}}
46+
// CHECK-NEXT: Submodule: FW
7147

48+
// RUN: %clang @%t/cas-FW.cc1.rsp
7249
// RUN: %clang @%t/cas-Mod.cc1.rsp
73-
// RUN: %clang @%t/cas-Spurious.cc1.rsp
7450
// RUN: %clang @%t/cas-tu.rsp
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// REQUIRES: ondisk_cas
2+
3+
// This test checks that we correctly handle situations where a spurious modular
4+
// dependency (1) turns otherwise textual dependency into modular (2).
5+
//
6+
// (1) For example #include <Spurious/Missing.h>, where the framework Spurious
7+
// has an umbrella header that does not include Missing.h, making it a textual
8+
// include instead.
9+
//
10+
// (2) For example when compiling the implementation file of a module Mod,
11+
// #included headers belonging to Mod are treated textually, unless some other
12+
// module already depends on Mod in its modular form.
13+
14+
// RUN: rm -rf %t
15+
// RUN: split-file %s %t
16+
// RUN: sed "s|DIR|%/t|g" %t/cdb.json.in > %t/cdb.json
17+
18+
//--- cdb.json.in
19+
[{
20+
"file": "DIR/tu.m",
21+
"directory": "DIR",
22+
"command": "clang -c DIR/tu.m -o DIR/tu.o -F DIR/frameworks -I DIR/include -fmodule-name=Mod -fmodules -fimplicit-module-maps -fmodules-cache-path=DIR/module-cache"
23+
}]
24+
25+
//--- frameworks/Spurious.framework/Modules/module.modulemap
26+
framework module Spurious {
27+
umbrella header "Spurious.h"
28+
module * { export * }
29+
}
30+
//--- frameworks/Spurious.framework/Headers/Spurious.h
31+
#include <Mod.h>
32+
//--- frameworks/Spurious.framework/Headers/Missing.h
33+
34+
//--- include/module.modulemap
35+
module Mod { header "Mod.h" }
36+
//--- include/Mod.h
37+
typedef int mod_int;
38+
39+
//--- tu.m
40+
#include <Spurious/Missing.h>
41+
#include <Mod.h>
42+
static mod_int x;
43+
44+
// RUN: clang-scan-deps -compilation-database %t/cdb.json \
45+
// RUN: -format experimental-full \
46+
// RUN: -module-files-dir %t/outputs > %t/deps.json
47+
48+
// RUN: %deps-to-rsp %t/deps.json --module-name=Mod > %t/Mod.cc1.rsp
49+
// RUN: %deps-to-rsp %t/deps.json --module-name=Spurious > %t/Spurious.cc1.rsp
50+
// RUN: %deps-to-rsp %t/deps.json --tu-index=0 > %t/tu.rsp
51+
52+
// RUN: %clang @%t/Mod.cc1.rsp
53+
// RUN: %clang @%t/Spurious.cc1.rsp
54+
// RUN: %clang @%t/tu.rsp
55+
56+
// RUN: clang-scan-deps -compilation-database %t/cdb.json \
57+
// RUN: -format experimental-include-tree-full -cas-path %t/cas \
58+
// RUN: -module-files-dir %t/cas-outputs > %t/cas-deps.json
59+
60+
// RUN: %deps-to-rsp %t/cas-deps.json --module-name=Mod > %t/cas-Mod.cc1.rsp
61+
// RUN: %deps-to-rsp %t/cas-deps.json --module-name=Spurious > %t/cas-Spurious.cc1.rsp
62+
// RUN: %deps-to-rsp %t/cas-deps.json --tu-index=0 > %t/cas-tu.rsp
63+
64+
// RUN: cat %t/cas-tu.rsp | sed -E 's|.*"-fcas-include-tree" "(llvmcas://[[:xdigit:]]+)".*|\1|' > %t/tu.casid
65+
// RUN: clang-cas-test -cas %t/cas -print-include-tree @%t/tu.casid > %t/tu-include-tree.txt
66+
// RUN: FileCheck %s -input-file %t/tu-include-tree.txt -DPREFIX=%/t
67+
// CHECK: [[PREFIX]]/tu.m llvmcas://
68+
// CHECK-NEXT: 1:1 <built-in> llvmcas://
69+
// CHECK-NEXT: 2:1 (Spurious import) (Module) Spurious.Missing [[PREFIX]]/frameworks/Spurious.framework/Headers/Missing.h llvmcas://
70+
// CHECK-NEXT: 3:1 (Module for visibility only) Mod
71+
72+
// RUN: %clang @%t/cas-Mod.cc1.rsp
73+
// RUN: %clang @%t/cas-Spurious.cc1.rsp
74+
// RUN: %clang @%t/cas-tu.rsp

0 commit comments

Comments
 (0)