Skip to content

Commit 4c4724c

Browse files
authored
Merge pull request swiftlang#31744 from xymus/dont-leak-ioi-in-spi
[Sema] Forbid leaking implementation-only imported types in SPI decls
2 parents 9a141a8 + 08abc0d commit 4c4724c

File tree

2 files changed

+75
-42
lines changed

2 files changed

+75
-42
lines changed

lib/Sema/TypeCheckAccess.cpp

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,6 @@ enum class DowngradeToWarning: bool {
4242
Yes
4343
};
4444

45-
/// A uniquely-typed boolean to reduce the chances of accidentally inverting
46-
/// a check.
47-
///
48-
/// \see checkTypeAccessImpl
49-
enum class FromSPI: bool {
50-
No,
51-
Yes
52-
};
53-
5445
/// Calls \p callback for each type in each requirement provided by
5546
/// \p source.
5647
static void forAllRequirementTypes(
@@ -87,7 +78,7 @@ class AccessControlCheckerBase {
8778

8879
void checkTypeAccessImpl(
8980
Type type, TypeRepr *typeRepr, AccessScope contextAccessScope,
90-
const DeclContext *useDC, bool mayBeInferred, FromSPI fromSPI,
81+
const DeclContext *useDC, bool mayBeInferred,
9182
llvm::function_ref<CheckTypeAccessCallback> diagnose);
9283

9384
void checkTypeAccess(
@@ -109,7 +100,7 @@ class AccessControlCheckerBase {
109100
llvm::function_ref<CheckTypeAccessCallback> diagnose) {
110101
forAllRequirementTypes(std::move(source), [&](Type type, TypeRepr *typeRepr) {
111102
checkTypeAccessImpl(type, typeRepr, accessScope, useDC,
112-
/*mayBeInferred*/false, FromSPI::No, diagnose);
103+
/*mayBeInferred*/false, diagnose);
113104
});
114105
}
115106

@@ -196,12 +187,9 @@ class TypeAccessScopeDiagnoser : private ASTWalker {
196187
/// using `Array` to mean `Array<Element>` in an extension of Array.) If
197188
/// \p typeRepr is known to be absent, it's okay to pass \c false for
198189
/// \p mayBeInferred.
199-
///
200-
/// If searching from an SPI context, pass \c FromSPI::YES for \p fromSPI.
201-
/// In this mode, all types must be public and diagnostic messages are adapted.
202190
void AccessControlCheckerBase::checkTypeAccessImpl(
203191
Type type, TypeRepr *typeRepr, AccessScope contextAccessScope,
204-
const DeclContext *useDC, bool mayBeInferred, FromSPI fromSPI,
192+
const DeclContext *useDC, bool mayBeInferred,
205193
llvm::function_ref<CheckTypeAccessCallback> diagnose) {
206194

207195
auto &Context = useDC->getASTContext();
@@ -310,9 +298,8 @@ void AccessControlCheckerBase::checkTypeAccess(
310298
context->getFormalAccessScope(
311299
context->getDeclContext(), checkUsableFromInline);
312300

313-
auto fromSPI = static_cast<FromSPI>(context->isSPI());
314301
checkTypeAccessImpl(type, typeRepr, contextAccessScope, DC, mayBeInferred,
315-
fromSPI, diagnose);
302+
diagnose);
316303
}
317304

318305
/// Highlights the given TypeRepr, and adds a note pointing to the type's
@@ -372,10 +359,6 @@ void AccessControlCheckerBase::checkGenericParamAccess(
372359
};
373360

374361
auto *DC = ownerDecl->getDeclContext();
375-
auto fromSPI = FromSPI::No;
376-
if (auto ownerValueDecl = dyn_cast<ValueDecl>(ownerDecl)) {
377-
fromSPI = static_cast<FromSPI>(ownerValueDecl->isSPI());
378-
}
379362

380363
for (auto param : *params) {
381364
if (param->getInherited().empty())
@@ -384,7 +367,7 @@ void AccessControlCheckerBase::checkGenericParamAccess(
384367
checkTypeAccessImpl(param->getInherited().front().getType(),
385368
param->getInherited().front().getTypeRepr(),
386369
accessScope, DC, /*mayBeInferred*/false,
387-
fromSPI, callback);
370+
callback);
388371
}
389372
callbackACEK = ACEK::Requirement;
390373

@@ -1483,11 +1466,14 @@ class UsableFromInlineChecker : public AccessControlCheckerBase,
14831466
}
14841467
};
14851468

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

14891474
void checkTypeImpl(
14901475
Type type, const TypeRepr *typeRepr, const SourceFile &SF,
1476+
const Decl *context,
14911477
const Diagnoser &diagnoser) {
14921478
// Don't bother checking errors.
14931479
if (type && type->hasError())
@@ -1496,18 +1482,19 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14961482
bool foundAnyIssues = false;
14971483

14981484
// Check the TypeRepr first (if present), because that will give us a
1499-
// better diagonstic.
1485+
// better diagnostic.
15001486
if (typeRepr) {
15011487
const_cast<TypeRepr *>(typeRepr)->walk(TypeReprIdentFinder(
15021488
[&](const ComponentIdentTypeRepr *component) {
1503-
ModuleDecl *M = component->getBoundDecl()->getModuleContext();
1504-
if (!SF.isImportedImplementationOnly(M) &&
1505-
!SF.isImportedAsSPI(component->getBoundDecl()))
1506-
return true;
1507-
1508-
diagnoser.diagnoseType(component->getBoundDecl(), component,
1509-
SF.isImportedImplementationOnly(M));
1510-
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+
15111498
// We still continue even in the diagnostic case to report multiple
15121499
// violations.
15131500
return true;
@@ -1525,19 +1512,19 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15251512

15261513
class ProblematicTypeFinder : public TypeDeclFinder {
15271514
const SourceFile &SF;
1515+
const Decl *context;
15281516
const Diagnoser &diagnoser;
15291517
public:
1530-
ProblematicTypeFinder(const SourceFile &SF, const Diagnoser &diagnoser)
1531-
: SF(SF), diagnoser(diagnoser) {}
1518+
ProblematicTypeFinder(const SourceFile &SF, const Decl *context, const Diagnoser &diagnoser)
1519+
: SF(SF), context(context), diagnoser(diagnoser) {}
15321520

15331521
void visitTypeDecl(const TypeDecl *typeDecl) {
15341522
ModuleDecl *M = typeDecl->getModuleContext();
1535-
if (!SF.isImportedImplementationOnly(M) &&
1536-
!SF.isImportedAsSPI(typeDecl))
1537-
return;
1538-
1539-
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr,
1540-
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);
15411528
}
15421529

15431530
void visitSubstitutionMap(SubstitutionMap subs) {
@@ -1597,15 +1584,15 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15971584
}
15981585
};
15991586

1600-
type.walk(ProblematicTypeFinder(SF, diagnoser));
1587+
type.walk(ProblematicTypeFinder(SF, context, diagnoser));
16011588
}
16021589

16031590
void checkType(
16041591
Type type, const TypeRepr *typeRepr, const Decl *context,
16051592
const Diagnoser &diagnoser) {
16061593
auto *SF = context->getDeclContext()->getParentSourceFile();
16071594
assert(SF && "checking a non-source declaration?");
1608-
return checkTypeImpl(type, typeRepr, *SF, diagnoser);
1595+
return checkTypeImpl(type, typeRepr, *SF, context, diagnoser);
16091596
}
16101597

16111598
void checkType(
@@ -1702,7 +1689,7 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
17021689
AccessScope accessScope =
17031690
VD->getFormalAccessScope(nullptr,
17041691
/*treatUsableFromInlineAsPublic*/true);
1705-
if (accessScope.isPublic() && !accessScope.isSPI())
1692+
if (accessScope.isPublic())
17061693
return false;
17071694

17081695
// 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)