Skip to content

Commit 7ac68ce

Browse files
committed
Sema: Check access, availability, and exportability of availability domains.
Teach Sema to diagnose the access level, exportability, and availability of availability domains that are referenced by `@available` attributes and `if #available` statements. Resolves rdar://159147207.
1 parent 9de8862 commit 7ac68ce

File tree

8 files changed

+530
-66
lines changed

8 files changed

+530
-66
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6989,6 +6989,17 @@ WARNING(attr_availability_expected_version_spec, none,
69896989
ERROR(attr_availability_requires_custom_availability, none,
69906990
"%0 requires '-enable-experimental-feature CustomAvailability'",
69916991
(AvailabilityDomain))
6992+
ERROR(attr_availability_domain_access, none,
6993+
"availability domain '%0' is "
6994+
"%select{private|fileprivate|internal|package|%error|%error}1 "
6995+
"and cannot be used in '%2' on "
6996+
"%select{private|fileprivate|internal|package|public|%error}3 %kind4",
6997+
(AvailabilityDomain, AccessLevel, DeclAttribute, AccessLevel,
6998+
const Decl *))
6999+
ERROR(attr_availability_domain_not_usable_from_inline, none,
7000+
"availability domain '%0' used in '%1' on %kind2 must be "
7001+
"'@usableFromInline' or public",
7002+
(AvailabilityDomain, DeclAttribute, const Decl *))
69927003

69937004
ERROR(availability_decl_unavailable, none,
69947005
"%0 is unavailable%select{ in %2|}1%select{|: %3}3",

lib/Sema/TypeCheckAccess.cpp

Lines changed: 168 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "swift/AST/Import.h"
2929
#include "swift/AST/ParameterList.h"
3030
#include "swift/AST/Pattern.h"
31+
#include "swift/AST/PrettyStackTrace.h"
3132
#include "swift/AST/TypeCheckRequests.h"
3233
#include "swift/Basic/Assertions.h"
3334
#include "clang/AST/DeclCXX.h"
@@ -70,10 +71,22 @@ static void forAllRequirementTypes(
7071
using CheckTypeAccessCallback =
7172
void(AccessScope, const TypeRepr *, DowngradeToWarning, ImportAccessLevel);
7273

74+
using CheckDeclAccessCallback = void(AccessScope, SourceLoc, ImportAccessLevel);
75+
7376
class AccessControlCheckerBase {
7477
protected:
7578
bool checkUsableFromInline;
7679

80+
bool shouldSkipChecking(const ValueDecl *decl);
81+
82+
/// Returns true if access checking ought to be skipped for the given
83+
/// `AccessScope`.
84+
bool shouldSkipAccessCheckingInContext(AccessScope contextAccessScope,
85+
const ASTContext &ctx);
86+
87+
ImportAccessLevel getImportAccessForDecl(const ValueDecl *decl,
88+
const DeclContext *useDC);
89+
7790
void checkTypeAccessImpl(
7891
Type type, TypeRepr *typeRepr, AccessScope contextAccessScope,
7992
const DeclContext *useDC, bool mayBeInferred,
@@ -102,6 +115,12 @@ class AccessControlCheckerBase {
102115
});
103116
}
104117

118+
void checkAvailabilityDomains(const Decl *D);
119+
120+
void checkDeclAccess(SourceLoc loc, const ValueDecl *decl,
121+
AccessScope contextAccessScope, const DeclContext *useDC,
122+
llvm::function_ref<CheckDeclAccessCallback> diagnose);
123+
105124
AccessControlCheckerBase(bool checkUsableFromInline)
106125
: checkUsableFromInline(checkUsableFromInline) {}
107126

@@ -117,10 +136,50 @@ class AccessControlCheckerBase {
117136
const ValueDecl *ownerDecl);
118137

119138
void checkGlobalActorAccess(const Decl *D);
139+
140+
void checkAvailabilityDomains(const Decl *D, AccessScope accessScope,
141+
AccessLevel contextAccess);
120142
};
121143

122144
} // end anonymous namespace
123145

146+
bool AccessControlCheckerBase::shouldSkipChecking(const ValueDecl *decl) {
147+
if (!checkUsableFromInline)
148+
return false;
149+
150+
if (decl->getFormalAccess() != AccessLevel::Internal &&
151+
decl->getFormalAccess() != AccessLevel::Package)
152+
return true;
153+
return !decl->isUsableFromInline();
154+
}
155+
156+
bool AccessControlCheckerBase::shouldSkipAccessCheckingInContext(
157+
AccessScope contextAccessScope, const ASTContext &ctx) {
158+
if (ctx.isAccessControlDisabled())
159+
return true;
160+
161+
// Don't spend time checking local declarations; this is always valid by the
162+
// time we get to this point.
163+
if (contextAccessScope.isInContext() &&
164+
contextAccessScope.getDeclContext()->isLocalContext())
165+
return true;
166+
167+
return false;
168+
}
169+
170+
ImportAccessLevel
171+
AccessControlCheckerBase::getImportAccessForDecl(const ValueDecl *decl,
172+
const DeclContext *useDC) {
173+
auto complainImport = decl->getImportAccessFrom(useDC);
174+
175+
// Don't complain about an import that doesn't restrict the access
176+
// level of the decl. This can happen with imported `package` decls.
177+
if (complainImport && complainImport->accessLevel >= decl->getFormalAccess())
178+
return std::nullopt;
179+
180+
return complainImport;
181+
}
182+
124183
/// Searches the given type representation for a `DeclRefTypeRepr` that is
125184
/// bound to a type declaration with the given access scope. The type
126185
/// representation is searched in source order. For example, nodes in
@@ -201,12 +260,7 @@ void AccessControlCheckerBase::checkTypeAccessImpl(
201260
llvm::function_ref<CheckTypeAccessCallback> diagnose) {
202261

203262
auto &Context = useDC->getASTContext();
204-
if (Context.isAccessControlDisabled())
205-
return;
206-
// Don't spend time checking local declarations; this is always valid by the
207-
// time we get to this point.
208-
if (contextAccessScope.isInContext() &&
209-
contextAccessScope.getDeclContext()->isLocalContext())
263+
if (shouldSkipAccessCheckingInContext(contextAccessScope, Context))
210264
return;
211265

212266
// Report where it was imported from.
@@ -229,7 +283,7 @@ void AccessControlCheckerBase::checkTypeAccessImpl(
229283
return TypeWalker::Action::Continue;
230284
}));
231285
}
232-
};
286+
}
233287

234288
AccessScope problematicAccessScope = AccessScope::getPublic();
235289

@@ -307,19 +361,36 @@ void AccessControlCheckerBase::checkTypeAccessImpl(
307361
const ValueDecl *VD = complainRepr->getBoundDecl();
308362
assert(VD &&
309363
"findTypeDeclWithAccessScope should return bound TypeReprs only");
310-
complainImport = VD->getImportAccessFrom(useDC);
311-
312-
// Don't complain about an import that doesn't restrict the access
313-
// level of the decl. This can happen with imported `package` decls.
314-
if (complainImport.has_value() &&
315-
complainImport->accessLevel >= VD->getFormalAccess())
316-
complainImport = std::nullopt;
364+
complainImport = getImportAccessForDecl(VD, useDC);
317365
}
318366

319367
diagnose(problematicAccessScope, complainRepr, downgradeToWarning,
320368
complainImport);
321369
}
322370

371+
void AccessControlCheckerBase::checkDeclAccess(
372+
SourceLoc loc, const ValueDecl *decl, AccessScope contextAccessScope,
373+
const DeclContext *useDC,
374+
llvm::function_ref<CheckDeclAccessCallback> diagnose) {
375+
376+
auto &ctx = useDC->getASTContext();
377+
if (shouldSkipAccessCheckingInContext(contextAccessScope, ctx))
378+
return;
379+
380+
recordRequiredImportAccessLevelForDecl(
381+
decl, useDC, contextAccessScope.accessLevelForDiagnostics(), loc);
382+
383+
AccessScope declAccessScope =
384+
decl->getFormalAccessScope(useDC, checkUsableFromInline);
385+
if (contextAccessScope.hasEqualDeclContextWith(declAccessScope) ||
386+
contextAccessScope.isChildOf(declAccessScope))
387+
return;
388+
389+
// The reference to the decl violates the rules of access control.
390+
ImportAccessLevel complainImport = getImportAccessForDecl(decl, useDC);
391+
diagnose(declAccessScope, loc, complainImport);
392+
}
393+
323394
/// Checks if the access scope of the type described by \p TL is valid for the
324395
/// type to be the type of \p context. If it isn't, calls \p diagnose with a
325396
/// TypeRepr representing the offending part of \p TL.
@@ -556,6 +627,50 @@ void AccessControlCheckerBase::checkGlobalActorAccess(const Decl *D) {
556627
});
557628
}
558629

630+
void AccessControlCheckerBase::checkAvailabilityDomains(
631+
const Decl *D, AccessScope accessScope, AccessLevel contextAccess) {
632+
auto &ctx = D->getASTContext();
633+
for (auto attr : D->getSemanticAvailableAttrs()) {
634+
if (auto *domainDecl = attr.getDomain().getDecl()) {
635+
checkDeclAccess(
636+
attr.getParsedAttr()->getDomainLoc(), domainDecl, accessScope,
637+
D->getDeclContext(),
638+
[&](AccessScope domainAccessScope, SourceLoc useLoc,
639+
ImportAccessLevel importLimit) {
640+
// FIXME: [availability] Improve diagnostics by indicating the decl
641+
// that the formal access is implied by. Enum cases, associated
642+
// types, protocol requirements, etc. inherit their access level
643+
// from their context.
644+
645+
if (checkUsableFromInline) {
646+
ctx.Diags.diagnose(
647+
useLoc, diag::attr_availability_domain_not_usable_from_inline,
648+
attr.getDomain(), attr.getParsedAttr(), D);
649+
noteLimitingImport(nullptr, ctx, importLimit, domainDecl);
650+
return;
651+
}
652+
653+
ctx.Diags.diagnose(useLoc, diag::attr_availability_domain_access,
654+
attr.getDomain(),
655+
domainAccessScope.accessLevelForDiagnostics(),
656+
attr.getParsedAttr(), contextAccess, D);
657+
noteLimitingImport(nullptr, ctx, importLimit, domainDecl);
658+
});
659+
}
660+
}
661+
}
662+
663+
void AccessControlCheckerBase::checkAvailabilityDomains(const Decl *D) {
664+
auto VD = dyn_cast<ValueDecl>(D->getAbstractSyntaxDeclForAttributes());
665+
if (!VD || shouldSkipChecking(VD))
666+
return;
667+
668+
AccessScope contextAccessScope =
669+
VD->getFormalAccessScope(VD->getDeclContext(), checkUsableFromInline);
670+
671+
checkAvailabilityDomains(VD, contextAccessScope, VD->getFormalAccess());
672+
}
673+
559674
namespace {
560675
class AccessControlChecker : public AccessControlCheckerBase,
561676
public DeclVisitor<AccessControlChecker> {
@@ -573,6 +688,7 @@ class AccessControlChecker : public AccessControlCheckerBase,
573688

574689
DeclVisitor<AccessControlChecker>::visit(D);
575690
checkGlobalActorAccess(D);
691+
checkAvailabilityDomains(D);
576692
}
577693

578694
// Force all kinds to be handled at a lower level.
@@ -1321,13 +1437,6 @@ class UsableFromInlineChecker : public AccessControlCheckerBase,
13211437
UsableFromInlineChecker()
13221438
: AccessControlCheckerBase(/*checkUsableFromInline=*/true) {}
13231439

1324-
static bool shouldSkipChecking(const ValueDecl *VD) {
1325-
if (VD->getFormalAccess() != AccessLevel::Internal &&
1326-
VD->getFormalAccess() != AccessLevel::Package)
1327-
return true;
1328-
return !VD->isUsableFromInline();
1329-
};
1330-
13311440
void visit(Decl *D) {
13321441
if (!D->getASTContext().isSwiftVersionAtLeast(4, 2))
13331442
return;
@@ -1341,6 +1450,7 @@ class UsableFromInlineChecker : public AccessControlCheckerBase,
13411450

13421451
DeclVisitor<UsableFromInlineChecker>::visit(D);
13431452
checkGlobalActorAccess(D);
1453+
checkAvailabilityDomains(D);
13441454
}
13451455

13461456
// Force all kinds to be handled at a lower level.
@@ -2139,10 +2249,22 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
21392249
}
21402250
}
21412251

2252+
void checkAvailabilityDomains(const Decl *D) {
2253+
D = D->getAbstractSyntaxDeclForAttributes();
2254+
for (auto attr : D->getSemanticAvailableAttrs()) {
2255+
if (auto *domainDecl = attr.getDomain().getDecl()) {
2256+
diagnoseDeclAvailability(domainDecl,
2257+
attr.getParsedAttr()->getDomainLoc(), nullptr,
2258+
Where, std::nullopt);
2259+
}
2260+
}
2261+
}
2262+
21422263
void visit(Decl *D) {
21432264
DeclVisitor<DeclAvailabilityChecker>::visit(D);
21442265
checkGlobalActor(D);
21452266
checkAttachedMacros(D);
2267+
checkAvailabilityDomains(D);
21462268
}
21472269

21482270
// Force all kinds to be handled at a lower level.
@@ -2372,14 +2494,10 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
23722494
}
23732495

23742496
void checkConstrainedExtensionRequirements(ExtensionDecl *ED,
2375-
bool hasExportedMembers) {
2497+
ExportabilityReason reason) {
23762498
if (!ED->getTrailingWhereClause())
23772499
return;
23782500

2379-
ExportabilityReason reason =
2380-
hasExportedMembers ? ExportabilityReason::ExtensionWithPublicMembers
2381-
: ExportabilityReason::ExtensionWithConditionalConformances;
2382-
23832501
forAllRequirementTypes(ED, [&](Type type, TypeRepr *typeRepr) {
23842502
checkType(type, typeRepr, ED, reason,
23852503
DeclAvailabilityFlag::DisableUnsafeChecking);
@@ -2423,7 +2541,23 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
24232541
// the 'where' clause must only name exported types.
24242542
Where = wasWhere.withExported(hasExportedMembers ||
24252543
!ED->getInherited().empty());
2426-
checkConstrainedExtensionRequirements(ED, hasExportedMembers);
2544+
ExportabilityReason reason =
2545+
hasExportedMembers
2546+
? ExportabilityReason::ExtensionWithPublicMembers
2547+
: ExportabilityReason::ExtensionWithConditionalConformances;
2548+
checkConstrainedExtensionRequirements(ED, reason);
2549+
2550+
// Diagnose the exportability of the availability domains referenced by the
2551+
// @available attributes attached to the extension.
2552+
if (Where.isExported()) {
2553+
for (auto availableAttr : ED->getSemanticAvailableAttrs()) {
2554+
if (auto *domainDecl = availableAttr.getDomain().getDecl()) {
2555+
TypeChecker::diagnoseDeclRefExportability(
2556+
availableAttr.getParsedAttr()->getDomainLoc(), domainDecl,
2557+
Where.withReason(reason));
2558+
}
2559+
}
2560+
}
24272561

24282562
// If we haven't already visited the extended nominal visit it here.
24292563
// This logic is too wide but prevents false reports of an unused public
@@ -2491,7 +2625,7 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
24912625

24922626
} // end anonymous namespace
24932627

2494-
static void checkExtensionGenericParamAccess(const ExtensionDecl *ED) {
2628+
static void checkExtensionAccess(const ExtensionDecl *ED) {
24952629
auto *AA = ED->getAttrs().getAttribute<AccessControlAttr>();
24962630
if (!AA)
24972631
return;
@@ -2522,8 +2656,11 @@ static void checkExtensionGenericParamAccess(const ExtensionDecl *ED) {
25222656
break;
25232657
}
25242658

2525-
AccessControlChecker().checkGenericParamAccess(
2526-
ED, ED, desiredAccessScope, userSpecifiedAccess);
2659+
auto accessChecker = AccessControlChecker();
2660+
accessChecker.checkGenericParamAccess(ED, ED, desiredAccessScope,
2661+
userSpecifiedAccess);
2662+
accessChecker.checkAvailabilityDomains(ED, desiredAccessScope,
2663+
userSpecifiedAccess);
25272664
}
25282665

25292666
DisallowedOriginKind swift::getDisallowedOriginKind(const Decl *decl,
@@ -2659,13 +2796,10 @@ void swift::checkAccessControl(Decl *D) {
26592796
AccessControlChecker(allowInlineable).visit(D);
26602797
UsableFromInlineChecker().visit(D);
26612798
} else if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
2662-
checkExtensionGenericParamAccess(ED);
2799+
checkExtensionAccess(ED);
26632800
registerPackageAccessForPackageExtendedType(ED);
26642801
}
26652802

2666-
if (isa<AccessorDecl>(D))
2667-
return;
2668-
26692803
auto where = ExportContext::forDeclSignature(D);
26702804
if (where.isImplicit())
26712805
return;

lib/Sema/TypeCheckAttr.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,9 @@ void AttributeChecker::visitSensitiveAttr(SensitiveAttr *attr) {
563563
}
564564

565565
void AttributeChecker::visitTransparentAttr(TransparentAttr *attr) {
566+
if (attr->isImplicit())
567+
return;
568+
566569
DeclContext *dc = D->getDeclContext();
567570
// Protocol declarations cannot be transparent.
568571
if (isa<ProtocolDecl>(dc))

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3719,11 +3719,11 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
37193719
TypeChecker::checkProtocolSelfRequirements(FD);
37203720
}
37213721

3722-
checkAccessControl(FD);
3723-
37243722
TypeChecker::checkParameterList(FD->getParameters(), FD);
37253723
}
37263724

3725+
checkAccessControl(FD);
3726+
37273727
TypeChecker::checkDeclAttributes(FD);
37283728
TypeChecker::checkDistributedFunc(FD);
37293729

0 commit comments

Comments
 (0)