Skip to content

Commit 08abc0d

Browse files
committed
[Sema] Fix leak of implementation-only imported types in SPI signatures
1 parent cfd1fdb commit 08abc0d

File tree

2 files changed

+69
-19
lines changed

2 files changed

+69
-19
lines changed

lib/Sema/TypeCheckAccess.cpp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,11 +1466,14 @@ class UsableFromInlineChecker : public AccessControlCheckerBase,
14661466
}
14671467
};
14681468

1469+
// Diagnose public APIs exposing types that are either imported as
1470+
// implementation-only or declared as SPI.
14691471
class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14701472
class Diagnoser;
14711473

14721474
void checkTypeImpl(
14731475
Type type, const TypeRepr *typeRepr, const SourceFile &SF,
1476+
const Decl *context,
14741477
const Diagnoser &diagnoser) {
14751478
// Don't bother checking errors.
14761479
if (type && type->hasError())
@@ -1483,14 +1486,15 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14831486
if (typeRepr) {
14841487
const_cast<TypeRepr *>(typeRepr)->walk(TypeReprIdentFinder(
14851488
[&](const ComponentIdentTypeRepr *component) {
1486-
ModuleDecl *M = component->getBoundDecl()->getModuleContext();
1487-
if (!SF.isImportedImplementationOnly(M) &&
1488-
!SF.isImportedAsSPI(component->getBoundDecl()))
1489-
return true;
1490-
1491-
diagnoser.diagnoseType(component->getBoundDecl(), component,
1492-
SF.isImportedImplementationOnly(M));
1493-
foundAnyIssues = true;
1489+
TypeDecl *typeDecl = component->getBoundDecl();
1490+
ModuleDecl *M = typeDecl->getModuleContext();
1491+
bool isImplementationOnly = SF.isImportedImplementationOnly(M);
1492+
if (isImplementationOnly ||
1493+
(SF.isImportedAsSPI(typeDecl) && !context->isSPI())) {
1494+
diagnoser.diagnoseType(typeDecl, component, isImplementationOnly);
1495+
foundAnyIssues = true;
1496+
}
1497+
14941498
// We still continue even in the diagnostic case to report multiple
14951499
// violations.
14961500
return true;
@@ -1508,19 +1512,19 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15081512

15091513
class ProblematicTypeFinder : public TypeDeclFinder {
15101514
const SourceFile &SF;
1515+
const Decl *context;
15111516
const Diagnoser &diagnoser;
15121517
public:
1513-
ProblematicTypeFinder(const SourceFile &SF, const Diagnoser &diagnoser)
1514-
: SF(SF), diagnoser(diagnoser) {}
1518+
ProblematicTypeFinder(const SourceFile &SF, const Decl *context, const Diagnoser &diagnoser)
1519+
: SF(SF), context(context), diagnoser(diagnoser) {}
15151520

15161521
void visitTypeDecl(const TypeDecl *typeDecl) {
15171522
ModuleDecl *M = typeDecl->getModuleContext();
1518-
if (!SF.isImportedImplementationOnly(M) &&
1519-
!SF.isImportedAsSPI(typeDecl))
1520-
return;
1521-
1522-
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr,
1523-
SF.isImportedImplementationOnly(M));
1523+
bool isImplementationOnly = SF.isImportedImplementationOnly(M);
1524+
if (isImplementationOnly ||
1525+
(SF.isImportedAsSPI(typeDecl) && !context->isSPI()))
1526+
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr,
1527+
isImplementationOnly);
15241528
}
15251529

15261530
void visitSubstitutionMap(SubstitutionMap subs) {
@@ -1580,15 +1584,15 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15801584
}
15811585
};
15821586

1583-
type.walk(ProblematicTypeFinder(SF, diagnoser));
1587+
type.walk(ProblematicTypeFinder(SF, context, diagnoser));
15841588
}
15851589

15861590
void checkType(
15871591
Type type, const TypeRepr *typeRepr, const Decl *context,
15881592
const Diagnoser &diagnoser) {
15891593
auto *SF = context->getDeclContext()->getParentSourceFile();
15901594
assert(SF && "checking a non-source declaration?");
1591-
return checkTypeImpl(type, typeRepr, *SF, diagnoser);
1595+
return checkTypeImpl(type, typeRepr, *SF, context, diagnoser);
15921596
}
15931597

15941598
void checkType(
@@ -1685,7 +1689,7 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
16851689
AccessScope accessScope =
16861690
VD->getFormalAccessScope(nullptr,
16871691
/*treatUsableFromInlineAsPublic*/true);
1688-
if (accessScope.isPublic() && !accessScope.isSPI())
1692+
if (accessScope.isPublic())
16891693
return false;
16901694

16911695
// Is this a stored property in a non-resilient struct or class?
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/// @_implementationOnly imported decls (SPI or not) should not be exposed in SPI.
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %target-swift-frontend -emit-module -DLIB %s -module-name Lib -emit-module-path %t/Lib.swiftmodule
5+
// RUN: %target-typecheck-verify-swift -DCLIENT -I %t
6+
7+
#if LIB
8+
9+
@_spi(A) public func spiFunc() {}
10+
11+
@_spi(A) public struct SPIStruct {
12+
public init() {}
13+
}
14+
15+
@_spi(A) public protocol SPIProtocol {}
16+
17+
public func ioiFunc() {}
18+
19+
public struct IOIStruct {
20+
public init() {}
21+
}
22+
23+
public protocol IOIProtocol {}
24+
25+
#elseif CLIENT
26+
27+
@_spi(A) @_implementationOnly import Lib
28+
29+
@_spi(B) public func leakSPIStruct(_ a: SPIStruct) -> SPIStruct { fatalError() } // expected-error 2 {{cannot use struct 'SPIStruct' here; 'Lib' has been imported as implementation-only}}
30+
@_spi(B) public func leakIOIStruct(_ a: IOIStruct) -> IOIStruct { fatalError() } // expected-error 2 {{cannot use struct 'IOIStruct' here; 'Lib' has been imported as implementation-only}}
31+
32+
public struct PublicStruct : IOIProtocol, SPIProtocol { // expected-error {{cannot use protocol 'IOIProtocol' here; 'Lib' has been imported as implementation-only}}
33+
// expected-error @-1 {{cannot use protocol 'SPIProtocol' here; 'Lib' has been imported as implementation-only}}
34+
public var spiStruct = SPIStruct() // expected-error {{cannot use struct 'SPIStruct' here; 'Lib' has been imported as implementation-only}}
35+
public var ioiStruct = IOIStruct() // expected-error {{cannot use struct 'IOIStruct' here; 'Lib' has been imported as implementation-only}}
36+
37+
@inlinable
38+
public func publicInlinable() {
39+
spiFunc() // expected-error {{global function 'spiFunc()' is '@_spi' and cannot be referenced from an '@inlinable' function}}
40+
ioiFunc() // expected-error {{global function 'ioiFunc()' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
41+
let s = SPIStruct() // expected-error {{struct 'SPIStruct' is '@_spi' and cannot be referenced from an '@inlinable' function}}
42+
let i = IOIStruct() // expected-error {{struct 'IOIStruct' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
43+
}
44+
}
45+
46+
#endif

0 commit comments

Comments
 (0)