Skip to content

Commit 91c62de

Browse files
authored
Merge pull request #70566 from xymus/spi-reexport-swift-only
Sema: Reexport SPI via Swift exported imports but not clang's
2 parents e431716 + 8b91e4c commit 91c62de

File tree

7 files changed

+160
-27
lines changed

7 files changed

+160
-27
lines changed

include/swift/AST/ImportCache.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ class ImportSet final :
5656
unsigned HasHeaderImportModule : 1;
5757
unsigned NumTopLevelImports : 31;
5858
unsigned NumTransitiveImports;
59+
unsigned NumTransitiveSwiftOnlyImports;
5960

6061
ImportSet(bool hasHeaderImportModule,
6162
ArrayRef<ImportedModule> topLevelImports,
62-
ArrayRef<ImportedModule> transitiveImports);
63+
ArrayRef<ImportedModule> transitiveImports,
64+
ArrayRef<ImportedModule> transitiveSwiftOnlyImports);
6365

6466
ImportSet(const ImportSet &) = delete;
6567
void operator=(const ImportSet &) = delete;
@@ -73,7 +75,8 @@ class ImportSet final :
7375
ArrayRef<ImportedModule> topLevelImports);
7476

7577
size_t numTrailingObjects(OverloadToken<ImportedModule>) const {
76-
return NumTopLevelImports + NumTransitiveImports;
78+
return NumTopLevelImports + NumTransitiveImports +
79+
NumTransitiveSwiftOnlyImports;
7780
}
7881

7982
/// This is a bit of a hack to make module name lookup work properly.
@@ -94,6 +97,12 @@ class ImportSet final :
9497
NumTransitiveImports};
9598
}
9699

100+
ArrayRef<ImportedModule> getTransitiveSwiftOnlyImports() const {
101+
return {getTrailingObjects<ImportedModule>() +
102+
NumTopLevelImports + NumTransitiveImports,
103+
NumTransitiveSwiftOnlyImports};
104+
}
105+
97106
ArrayRef<ImportedModule> getAllImports() const {
98107
return {getTrailingObjects<ImportedModule>(),
99108
NumTopLevelImports + NumTransitiveImports};
@@ -115,6 +124,9 @@ class alignas(ImportedModule) ImportCache {
115124
const ModuleDecl *,
116125
const DeclContext *>,
117126
ArrayRef<ImportPath::Access>> ShadowCache;
127+
llvm::DenseMap<std::tuple<const ModuleDecl *,
128+
const DeclContext *>,
129+
bool> SwiftOnlyCache;
118130

119131
ImportPath::Access EmptyAccessPath;
120132

@@ -142,6 +154,12 @@ class alignas(ImportedModule) ImportCache {
142154
return !getAllVisibleAccessPaths(mod, dc).empty();
143155
}
144156

157+
/// Is `mod` imported from `dc` via a purely Swift access path?
158+
/// Always returns false if `dc` is a non-Swift module and only takes
159+
/// into account re-exports declared from Swift modules for transitive imports.
160+
bool isImportedByViaSwiftOnly(const ModuleDecl *mod,
161+
const DeclContext *dc);
162+
145163
/// Returns all access paths in 'mod' that are visible from 'dc' if we
146164
/// subtract imports of 'other'.
147165
ArrayRef<ImportPath::Access>

include/swift/AST/Module.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,10 +1087,6 @@ class ModuleDecl
10871087
/// Returns the associated clang module if one exists.
10881088
const clang::Module *findUnderlyingClangModule() const;
10891089

1090-
/// Does this module or the underlying clang module defines export_as with
1091-
/// a value corresponding to the \p other module?
1092-
bool isExportedAs(const ModuleDecl *other) const;
1093-
10941090
/// Returns a generator with the components of this module's full,
10951091
/// hierarchical name.
10961092
///

lib/AST/ImportCache.cpp

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,21 @@ using namespace namelookup;
2727

2828
ImportSet::ImportSet(bool hasHeaderImportModule,
2929
ArrayRef<ImportedModule> topLevelImports,
30-
ArrayRef<ImportedModule> transitiveImports)
30+
ArrayRef<ImportedModule> transitiveImports,
31+
ArrayRef<ImportedModule> transitiveSwiftOnlyImports)
3132
: HasHeaderImportModule(hasHeaderImportModule),
3233
NumTopLevelImports(topLevelImports.size()),
33-
NumTransitiveImports(transitiveImports.size()) {
34+
NumTransitiveImports(transitiveImports.size()),
35+
NumTransitiveSwiftOnlyImports(transitiveSwiftOnlyImports.size()) {
3436
auto buffer = getTrailingObjects<ImportedModule>();
3537
std::uninitialized_copy(topLevelImports.begin(), topLevelImports.end(),
3638
buffer);
3739
std::uninitialized_copy(transitiveImports.begin(), transitiveImports.end(),
3840
buffer + topLevelImports.size());
41+
std::uninitialized_copy(transitiveSwiftOnlyImports.begin(),
42+
transitiveSwiftOnlyImports.end(),
43+
buffer + topLevelImports.size() +
44+
transitiveImports.size());
3945

4046
#ifndef NDEBUG
4147
llvm::SmallDenseSet<ImportedModule, 8> unique;
@@ -47,6 +53,15 @@ ImportSet::ImportSet(bool hasHeaderImportModule,
4753
auto result = unique.insert(import).second;
4854
assert(result && "Duplicate imports in import set");
4955
}
56+
57+
unique.clear();
58+
for (auto import : topLevelImports) {
59+
unique.insert(import);
60+
}
61+
for (auto import : transitiveSwiftOnlyImports) {
62+
auto result = unique.insert(import).second;
63+
assert(result && "Duplicate imports in import set");
64+
}
5065
#endif
5166
}
5267

@@ -82,10 +97,14 @@ void ImportSet::dump() const {
8297
}
8398

8499
static void collectExports(ImportedModule next,
85-
SmallVectorImpl<ImportedModule> &stack) {
100+
SmallVectorImpl<ImportedModule> &stack,
101+
bool onlySwiftExports) {
86102
SmallVector<ImportedModule, 4> exports;
87103
next.importedModule->getImportedModulesForLookup(exports);
88104
for (auto exported : exports) {
105+
if (onlySwiftExports && exported.importedModule->isNonSwiftModule())
106+
continue;
107+
89108
if (next.accessPath.empty())
90109
stack.push_back(exported);
91110
else if (exported.accessPath.empty()) {
@@ -135,7 +154,7 @@ ImportCache::getImportSet(ASTContext &ctx,
135154

136155
SmallVector<ImportedModule, 4> stack;
137156
for (auto next : topLevelImports) {
138-
collectExports(next, stack);
157+
collectExports(next, stack, /*onlySwiftExports*/false);
139158
}
140159

141160
while (!stack.empty()) {
@@ -148,7 +167,26 @@ ImportCache::getImportSet(ASTContext &ctx,
148167
if (next.importedModule == headerImportModule)
149168
hasHeaderImportModule = true;
150169

151-
collectExports(next, stack);
170+
collectExports(next, stack, /*onlySwiftExports*/false);
171+
}
172+
173+
// Now collect transitive imports through Swift reexported imports only.
174+
SmallVector<ImportedModule, 4> transitiveSwiftOnlyImports;
175+
visited.clear();
176+
stack.clear();
177+
for (auto next : topLevelImports) {
178+
if (!visited.insert(next).second)
179+
continue;
180+
collectExports(next, stack, /*onlySwiftExports*/true);
181+
}
182+
183+
while (!stack.empty()) {
184+
auto next = stack.pop_back_val();
185+
if (!visited.insert(next).second)
186+
continue;
187+
188+
transitiveSwiftOnlyImports.push_back(next);
189+
collectExports(next, stack, /*onlySwiftExports*/true);
152190
}
153191

154192
// Find the insert position again, in case the above traversal invalidated
@@ -157,12 +195,15 @@ ImportCache::getImportSet(ASTContext &ctx,
157195
if (ImportSet *result = ImportSets.FindNodeOrInsertPos(ID, InsertPos))
158196
return *result;
159197

160-
size_t bytes = ImportSet::totalSizeToAlloc<ImportedModule>(topLevelImports.size() + transitiveImports.size());
198+
size_t bytes = ImportSet::totalSizeToAlloc<ImportedModule>(
199+
topLevelImports.size() + transitiveImports.size() +
200+
transitiveSwiftOnlyImports.size());
161201
void *mem = ctx.Allocate(bytes, alignof(ImportSet), AllocationArena::Permanent);
162202

163203
auto *result = new (mem) ImportSet(hasHeaderImportModule,
164204
topLevelImports,
165-
transitiveImports);
205+
transitiveImports,
206+
transitiveSwiftOnlyImports);
166207
ImportSets.InsertNode(result, InsertPos);
167208

168209
return *result;
@@ -249,6 +290,36 @@ ImportCache::getAllVisibleAccessPaths(const ModuleDecl *mod,
249290
return result;
250291
}
251292

293+
bool ImportCache::isImportedByViaSwiftOnly(const ModuleDecl *mod,
294+
const DeclContext *dc) {
295+
dc = dc->getModuleScopeContext();
296+
if (dc->getParentModule()->isNonSwiftModule())
297+
return false;
298+
299+
auto &ctx = mod->getASTContext();
300+
auto key = std::make_pair(mod, dc);
301+
auto found = SwiftOnlyCache.find(key);
302+
if (found != SwiftOnlyCache.end()) {
303+
if (ctx.Stats)
304+
++ctx.Stats->getFrontendCounters().ModuleVisibilityCacheHit;
305+
return found->second;
306+
}
307+
308+
if (ctx.Stats)
309+
++ctx.Stats->getFrontendCounters().ModuleVisibilityCacheMiss;
310+
311+
bool result = false;
312+
for (auto next : getImportSet(dc).getTransitiveSwiftOnlyImports()) {
313+
if (next.importedModule == mod) {
314+
result = true;
315+
break;
316+
}
317+
}
318+
319+
SwiftOnlyCache[key] = result;
320+
return result;
321+
}
322+
252323
ArrayRef<ImportPath::Access>
253324
ImportCache::getAllAccessPathsNotShadowedBy(const ModuleDecl *mod,
254325
const ModuleDecl *other,
@@ -307,7 +378,7 @@ ImportCache::getAllAccessPathsNotShadowedBy(const ModuleDecl *mod,
307378
accessPaths.push_back(next.accessPath);
308379
}
309380

310-
collectExports(next, stack);
381+
collectExports(next, stack, /*onlySwiftExports*/false);
311382
}
312383

313384
auto result = allocateArray(ctx, accessPaths);

lib/AST/Module.cpp

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2716,14 +2716,6 @@ const clang::Module *ModuleDecl::findUnderlyingClangModule() const {
27162716
return nullptr;
27172717
}
27182718

2719-
bool ModuleDecl::isExportedAs(const ModuleDecl *other) const {
2720-
auto clangModule = findUnderlyingClangModule();
2721-
if (!clangModule)
2722-
return false;
2723-
2724-
return other->getRealName().str() == clangModule->ExportAsModule;
2725-
}
2726-
27272719
void ModuleDecl::collectBasicSourceFileInfo(
27282720
llvm::function_ref<void(const BasicSourceFileInfo &)> callback) const {
27292721
for (const FileUnit *fileUnit : getFiles()) {
@@ -3519,8 +3511,8 @@ void SourceFile::lookupImportedSPIGroups(
35193511
for (auto &import : *Imports) {
35203512
if (import.options.contains(ImportFlags::SPIAccessControl) &&
35213513
(importedModule == import.module.importedModule ||
3522-
(imports.isImportedBy(importedModule, import.module.importedModule) &&
3523-
importedModule->isExportedAs(import.module.importedModule)))) {
3514+
imports.isImportedByViaSwiftOnly(importedModule,
3515+
import.module.importedModule))) {
35243516
spiGroups.insert(import.spiGroups.begin(), import.spiGroups.end());
35253517
}
35263518
}

lib/Serialization/SerializedModuleLoader.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1725,7 +1725,8 @@ void SerializedASTFile::lookupImportedSPIGroups(
17251725

17261726
if (dep.Import->importedModule == importedModule ||
17271727
(imports.isImportedBy(importedModule, dep.Import->importedModule) &&
1728-
importedModule->isExportedAs(dep.Import->importedModule))) {
1728+
imports.isImportedByViaSwiftOnly(importedModule,
1729+
dep.Import->importedModule))) {
17291730
spiGroups.insert(dep.spiGroups.begin(), dep.spiGroups.end());
17301731
}
17311732
}

test/SPI/accidental-reexport.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/// Guard that we don't reexport the SPIs accidentally through the common
2+
/// reexported imports between clang modules.
3+
4+
// RUN: %empty-directory(%t)
5+
// RUN: split-file %s %t
6+
7+
// RUN: %target-swift-frontend -emit-module %t/Overlayed.swift -I %t -o %t
8+
// RUN: %target-swift-frontend -typecheck -verify %t/Client.swift -I %t
9+
// RUN: %target-swift-frontend -typecheck -verify %t/LowerClient.swift -I %t
10+
11+
//--- module.modulemap
12+
module Overlayed {
13+
header "Overlayed.h"
14+
}
15+
16+
module Middle {
17+
header "Middle.h"
18+
export *
19+
}
20+
21+
module LowerMiddle {
22+
header "LowerMiddle.h"
23+
export *
24+
}
25+
26+
//--- Overlayed.h
27+
//--- Overlayed.swift
28+
@_exported import Overlayed
29+
30+
public func funcPublic() {}
31+
32+
@_spi(X)
33+
public func funcSPI() {}
34+
35+
//--- Middle.h
36+
#include <Overlayed.h>
37+
38+
//--- LowerMiddle.h
39+
#include <Middle.h>
40+
41+
//--- Client.swift
42+
@_spi(X) import Middle
43+
44+
funcPublic()
45+
funcSPI() // expected-error {{cannot find 'funcSPI' in scope}}
46+
47+
//--- LowerClient.swift
48+
@_spi(X) import LowerMiddle
49+
50+
funcPublic()
51+
funcSPI() // expected-error {{cannot find 'funcSPI' in scope}}

test/SPI/reexported-spi-groups.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
// RUN: %t/Client_FileA.swift %t/Client_FileB.swift\
5656
// RUN: -swift-version 5 -I %t -verify
5757

58-
/// Test that SPIs aren't avaible from a reexport without export_as
58+
/// SPIs are now reexported by any `@_exported` from Swift modules, no matter
59+
/// if `export_as` is present or not.
5960
// RUN: %target-swift-frontend -typecheck \
6061
// RUN: %t/NonExportedAsClient.swift \
6162
// RUN: -swift-version 5 -I %t -verify
@@ -92,6 +93,8 @@ public func exportedPublicFunc() {}
9293

9394
@_spi(X) public struct ExportedSpiType {}
9495

96+
@_spi(O) public func exportedSpiFuncOtherGroup() {}
97+
9598
//--- Exporter.swift
9699

97100
@_exported import Exported
@@ -130,6 +133,7 @@ public func clientA() {
130133
exportedPublicFunc()
131134
exportedSpiFunc()
132135
exporterSpiFunc()
136+
exportedSpiFuncOtherGroup() // expected-error {{cannot find 'exportedSpiFuncOtherGroup' in scope}}
133137
}
134138

135139
@inlinable
@@ -159,7 +163,7 @@ public func clientB() {
159163

160164
public func client() {
161165
exportedPublicFunc()
162-
exportedSpiFunc() // expected-error {{cannot find 'exportedSpiFunc' in scope}}
166+
exportedSpiFunc()
163167
exporterSpiFunc()
164168
}
165169

0 commit comments

Comments
 (0)