Skip to content

Commit 6468fbd

Browse files
authored
Merge pull request #31761 from xymus/spi-checks-5.3
[5.3] Check SPI usage of implementation-only types, in frozen types and on protocol requirements
2 parents 502f2ec + 0a945fa commit 6468fbd

File tree

7 files changed

+226
-20
lines changed

7 files changed

+226
-20
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,13 @@ ERROR(spi_attribute_on_non_public,none,
17051705
"cannot be declared '@_spi' because only public and open "
17061706
"declarations can be '@_spi'",
17071707
(AccessLevel, DescriptiveDeclKind))
1708+
ERROR(spi_attribute_on_protocol_requirement,none,
1709+
"protocol requirement %0 cannot be declared '@_spi' without "
1710+
"a default implementation in a protocol extension",
1711+
(DeclName))
1712+
ERROR(spi_attribute_on_frozen_stored_properties,none,
1713+
"stored property %0 cannot be declared '@_spi' in a '@frozen' struct",
1714+
(DeclName))
17081715

17091716
// Opaque return types
17101717
ERROR(opaque_type_invalid_constraint,none,

lib/Sema/TypeCheckAccess.cpp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,11 +1483,14 @@ class UsableFromInlineChecker : public AccessControlCheckerBase,
14831483
}
14841484
};
14851485

1486+
// Diagnose public APIs exposing types that are either imported as
1487+
// implementation-only or declared as SPI.
14861488
class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
14871489
class Diagnoser;
14881490

14891491
void checkTypeImpl(
14901492
Type type, const TypeRepr *typeRepr, const SourceFile &SF,
1493+
const Decl *context,
14911494
const Diagnoser &diagnoser) {
14921495
// Don't bother checking errors.
14931496
if (type && type->hasError())
@@ -1500,14 +1503,15 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15001503
if (typeRepr) {
15011504
const_cast<TypeRepr *>(typeRepr)->walk(TypeReprIdentFinder(
15021505
[&](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;
1506+
TypeDecl *typeDecl = component->getBoundDecl();
1507+
ModuleDecl *M = typeDecl->getModuleContext();
1508+
bool isImplementationOnly = SF.isImportedImplementationOnly(M);
1509+
if (isImplementationOnly ||
1510+
(SF.isImportedAsSPI(typeDecl) && !context->isSPI())) {
1511+
diagnoser.diagnoseType(typeDecl, component, isImplementationOnly);
1512+
foundAnyIssues = true;
1513+
}
1514+
15111515
// We still continue even in the diagnostic case to report multiple
15121516
// violations.
15131517
return true;
@@ -1525,19 +1529,19 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15251529

15261530
class ProblematicTypeFinder : public TypeDeclFinder {
15271531
const SourceFile &SF;
1532+
const Decl *context;
15281533
const Diagnoser &diagnoser;
15291534
public:
1530-
ProblematicTypeFinder(const SourceFile &SF, const Diagnoser &diagnoser)
1531-
: SF(SF), diagnoser(diagnoser) {}
1535+
ProblematicTypeFinder(const SourceFile &SF, const Decl *context, const Diagnoser &diagnoser)
1536+
: SF(SF), context(context), diagnoser(diagnoser) {}
15321537

15331538
void visitTypeDecl(const TypeDecl *typeDecl) {
15341539
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));
1540+
bool isImplementationOnly = SF.isImportedImplementationOnly(M);
1541+
if (isImplementationOnly ||
1542+
(SF.isImportedAsSPI(typeDecl) && !context->isSPI()))
1543+
diagnoser.diagnoseType(typeDecl, /*typeRepr*/nullptr,
1544+
isImplementationOnly);
15411545
}
15421546

15431547
void visitSubstitutionMap(SubstitutionMap subs) {
@@ -1597,15 +1601,15 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
15971601
}
15981602
};
15991603

1600-
type.walk(ProblematicTypeFinder(SF, diagnoser));
1604+
type.walk(ProblematicTypeFinder(SF, context, diagnoser));
16011605
}
16021606

16031607
void checkType(
16041608
Type type, const TypeRepr *typeRepr, const Decl *context,
16051609
const Diagnoser &diagnoser) {
16061610
auto *SF = context->getDeclContext()->getParentSourceFile();
16071611
assert(SF && "checking a non-source declaration?");
1608-
return checkTypeImpl(type, typeRepr, *SF, diagnoser);
1612+
return checkTypeImpl(type, typeRepr, *SF, context, diagnoser);
16091613
}
16101614

16111615
void checkType(
@@ -1702,7 +1706,7 @@ class ExportabilityChecker : public DeclVisitor<ExportabilityChecker> {
17021706
AccessScope accessScope =
17031707
VD->getFormalAccessScope(nullptr,
17041708
/*treatUsableFromInlineAsPublic*/true);
1705-
if (accessScope.isPublic() && !accessScope.isSPI())
1709+
if (accessScope.isPublic())
17061710
return false;
17071711

17081712
// Is this a stored property in a non-resilient struct or class?

lib/Sema/TypeCheckAttr.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,13 +862,60 @@ void AttributeChecker::visitSetterAccessAttr(
862862

863863
void AttributeChecker::visitSPIAccessControlAttr(SPIAccessControlAttr *attr) {
864864
if (auto VD = dyn_cast<ValueDecl>(D)) {
865+
// VD must be public or open to use an @_spi attribute.
865866
auto declAccess = VD->getFormalAccess();
866867
if (declAccess < AccessLevel::Public) {
867868
diagnoseAndRemoveAttr(attr,
868869
diag::spi_attribute_on_non_public,
869870
declAccess,
870871
D->getDescriptiveKind());
871872
}
873+
874+
// If VD is a public protocol requirement it can be SPI only if there's
875+
// a default implementation.
876+
if (auto protocol = dyn_cast<ProtocolDecl>(D->getDeclContext())) {
877+
auto implementations = TypeChecker::lookupMember(
878+
D->getDeclContext(),
879+
protocol->getDeclaredType(),
880+
VD->createNameRef(),
881+
NameLookupFlags::ProtocolMembers);
882+
bool hasDefaultImplementation = llvm::any_of(implementations,
883+
[&](const LookupResultEntry &entry) {
884+
auto entryDecl = entry.getValueDecl();
885+
auto DC = entryDecl->getDeclContext();
886+
auto extension = dyn_cast<ExtensionDecl>(DC);
887+
888+
// The implementation must be defined in the same module in
889+
// an unconstrained extension.
890+
if (!extension ||
891+
extension->getParentModule() != protocol->getParentModule() ||
892+
extension->isConstrainedExtension())
893+
return false;
894+
895+
// For computed properties and subscripts, check that the default
896+
// implementation defines `set` if the protocol declares it.
897+
if (auto protoStorage = dyn_cast<AbstractStorageDecl>(VD))
898+
if (auto entryStorage = dyn_cast<AbstractStorageDecl>(entryDecl))
899+
if (protoStorage->getAccessor(AccessorKind::Set) &&
900+
!entryStorage->getAccessor(AccessorKind::Set))
901+
return false;
902+
903+
return true;
904+
});
905+
906+
if (!hasDefaultImplementation)
907+
diagnoseAndRemoveAttr(attr,
908+
diag::spi_attribute_on_protocol_requirement,
909+
VD->getName());
910+
}
911+
912+
// Forbid stored properties marked SPI in frozen types.
913+
if (auto property = dyn_cast<AbstractStorageDecl>(VD))
914+
if (auto DC = dyn_cast<NominalTypeDecl>(D->getDeclContext()))
915+
if (property->hasStorage() && !DC->isFormallyResilient())
916+
diagnoseAndRemoveAttr(attr,
917+
diag::spi_attribute_on_frozen_stored_properties,
918+
VD->getName());
872919
}
873920
}
874921

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

test/SPI/local_spi_decls.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ func inlinable() -> SPIClass { // expected-error {{class 'SPIClass' is '@_spi' a
3131
@_spi(S) public struct SPIStruct {} // expected-note 2 {{struct 'SPIStruct' is not '@usableFromInline' or public}}
3232

3333
@frozen public struct FrozenStruct {
34-
@_spi(S) public var asdf = SPIStruct() // expected-error {{struct 'SPIStruct' is '@_spi' and cannot be referenced from a property initializer in a '@frozen' type}}
34+
@_spi(S) public var spiInFrozen = SPIStruct() // expected-error {{struct 'SPIStruct' is '@_spi' and cannot be referenced from a property initializer in a '@frozen' type}}
35+
// expected-error @-1 {{stored property 'spiInFrozen' cannot be declared '@_spi' in a '@frozen' struct}}
36+
3537
var asdf = SPIStruct() // expected-error {{struct 'SPIStruct' is '@_spi' and cannot be referenced from a property initializer in a '@frozen' type}}
3638
}
3739

test/SPI/protocol_requirement.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Test limitations on SPI protocol requirements.
2+
3+
// RUN: %target-typecheck-verify-swift
4+
5+
// Reject SPI protocol requirements without a default implementation.
6+
public protocol PublicProtoRejected {
7+
@_spi(Private) // expected-error{{protocol requirement 'reqWithoutDefault()' cannot be declared '@_spi' without a default implementation in a protocol extension}}
8+
func reqWithoutDefault()
9+
10+
@_spi(Private) // expected-error{{protocol requirement 'property' cannot be declared '@_spi' without a default implementation in a protocol extension}}
11+
var property: Int { get set }
12+
13+
@_spi(Private) // expected-error{{protocol requirement 'propertyWithoutSetter' cannot be declared '@_spi' without a default implementation in a protocol extension}}
14+
var propertyWithoutSetter: Int { get set }
15+
16+
@_spi(Private) // expected-error{{protocol requirement 'subscript(_:)' cannot be declared '@_spi' without a default implementation in a protocol extension}}
17+
subscript(index: Int) -> Int { get set }
18+
19+
@_spi(Private) // expected-error{{protocol requirement 'init()' cannot be declared '@_spi' without a default implementation in a protocol extension}}
20+
init()
21+
22+
@_spi(Private) // expected-error{{'@_spi' attribute cannot be applied to this declaration}}
23+
associatedtype T
24+
}
25+
26+
extension PublicProtoRejected {
27+
@_spi(Private)
28+
public var propertyWithoutSetter: Int { get { return 42 } }
29+
}
30+
31+
extension PublicProtoRejected where Self : Equatable {
32+
@_spi(Private)
33+
public func reqWithoutDefault() {
34+
// constrainted implementation
35+
}
36+
}
37+
38+
// Accept SPI protocol requirements with an implementation.
39+
public protocol PublicProto {
40+
@_spi(Private)
41+
func reqWithDefaultImplementation()
42+
43+
@_spi(Private)
44+
var property: Int { get set }
45+
46+
@_spi(Private)
47+
subscript(index: Int) -> Int { get set }
48+
49+
@_spi(Private)
50+
init()
51+
}
52+
53+
extension PublicProto {
54+
@_spi(Private)
55+
public func reqWithDefaultImplementation() { }
56+
57+
@_spi(Private)
58+
public var property: Int {
59+
get { return 42 }
60+
set { }
61+
}
62+
63+
@_spi(Private)
64+
public subscript(index: Int) -> Int {
65+
get { return 42 }
66+
set { }
67+
}
68+
69+
@_spi(Private)
70+
public init() { }
71+
}

test/SPI/resilience.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Test limits of SPI members in frozen types.
2+
3+
// RUN: %target-typecheck-verify-swift
4+
5+
@frozen
6+
public struct FrozenStruct {
7+
public var okProperty: Int
8+
9+
@_spi(S)
10+
public var okComputedProperty: Int { get { return 42 } }
11+
12+
@_spi(S) // expected-error {{stored property 'spiProperty' cannot be declared '@_spi' in a '@frozen' struct}}
13+
public var spiProperty: Int
14+
15+
@_spi(S) // expected-error {{stored property 'spiPropertySet' cannot be declared '@_spi' in a '@frozen' struct}}
16+
public var spiPropertySet = 4
17+
18+
@_spi(S) // expected-error {{stored property 'spiPropertyDoubleErrors' cannot be declared '@_spi' in a '@frozen' struct}}
19+
// expected-error @-1 {{internal property cannot be declared '@_spi' because only public and open declarations can be '@_spi'}}
20+
var spiPropertyDoubleErrors: Int
21+
}
22+
23+
public struct UnfrozenStruct {
24+
@_spi(S)
25+
public var spiProperty: Int
26+
27+
@_spi(S)
28+
public var spiPropertySet = 4
29+
}

0 commit comments

Comments
 (0)