Skip to content

Commit cd22c5d

Browse files
committed
Use access scopes for the hard cases in ValueDecl::isAccessibleFrom
This function (actually checkAccess) was relying on some implicit assumptions that aren't actually valid in all cases. When they're not, just fall back to a slower but more correct implementation; when they are, assert that the two implementations get the same answer. This allows us to get rid of adjustAccessLevelForProtocolExtension (see previous commit), though unfortunately not all of the associated hack. The diff is bigger than I'd like because it includes moving functions from NameLookup.cpp into Decl.cpp, but most of those didn't change. - checkAccess only changed in the one if branch for protocols - ValueDecl::isAccessibleFrom just added the assertion - AbstractStorageDecl::isSetterAccessibleFrom did not change No expected functionality change.
1 parent fef3a37 commit cd22c5d

File tree

4 files changed

+215
-105
lines changed

4 files changed

+215
-105
lines changed

lib/AST/Decl.cpp

Lines changed: 172 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2280,24 +2280,38 @@ static AccessLevel getTestableAccess(const ValueDecl *decl) {
22802280
return AccessLevel::Public;
22812281
}
22822282

2283-
/// Like ValueDecl::getFormalAccess, but takes into account the parameters that
2284-
/// ValueDecl::getFormalAccessScope is aware of.
2285-
static AccessLevel
2286-
getAdjustedFormalAccess(const ValueDecl *VD, const DeclContext *useDC,
2287-
bool treatUsableFromInlineAsPublic) {
2288-
AccessLevel result = VD->getFormalAccess();
2283+
/// Adjust \p access based on whether \p VD is \@usableFromInline or has been
2284+
/// testably imported from \p useDC.
2285+
///
2286+
/// \p access isn't always just `VD->getFormalAccess()` because this adjustment
2287+
/// may be for a write, in which case the setter's access might be used instead.
2288+
static AccessLevel getAdjustedFormalAccess(const ValueDecl *VD,
2289+
AccessLevel access,
2290+
const DeclContext *useDC,
2291+
bool treatUsableFromInlineAsPublic) {
22892292
if (treatUsableFromInlineAsPublic &&
2290-
result == AccessLevel::Internal &&
2293+
access == AccessLevel::Internal &&
22912294
VD->isUsableFromInline()) {
22922295
return AccessLevel::Public;
22932296
}
2294-
if (useDC && (result == AccessLevel::Internal ||
2295-
result == AccessLevel::Public)) {
2297+
2298+
if (useDC && (access == AccessLevel::Internal ||
2299+
access == AccessLevel::Public)) {
22962300
if (auto *useSF = dyn_cast<SourceFile>(useDC->getModuleScopeContext()))
22972301
if (useSF->hasTestableImport(VD->getModuleContext()))
22982302
return getTestableAccess(VD);
22992303
}
2300-
return result;
2304+
2305+
return access;
2306+
}
2307+
2308+
/// Convenience overload that uses `VD->getFormalAccess()` as the access to
2309+
/// adjust.
2310+
static AccessLevel
2311+
getAdjustedFormalAccess(const ValueDecl *VD, const DeclContext *useDC,
2312+
bool treatUsableFromInlineAsPublic) {
2313+
return getAdjustedFormalAccess(VD, VD->getFormalAccess(), useDC,
2314+
treatUsableFromInlineAsPublic);
23012315
}
23022316

23032317
AccessLevel ValueDecl::getEffectiveAccess() const {
@@ -2370,31 +2384,39 @@ bool ValueDecl::hasOpenAccess(const DeclContext *useDC) const {
23702384
return access == AccessLevel::Open;
23712385
}
23722386

2373-
AccessScope
2374-
ValueDecl::getFormalAccessScope(const DeclContext *useDC,
2375-
bool treatUsableFromInlineAsPublic) const {
2376-
const DeclContext *result = getDeclContext();
2377-
AccessLevel access = getAdjustedFormalAccess(this, useDC,
2387+
/// Given the formal access level for using \p VD, compute the scope where
2388+
/// \p VD may be accessed, taking \@usableFromInline, \@testable imports,
2389+
/// and enclosing access levels into account.
2390+
///
2391+
/// \p access isn't always just `VD->getFormalAccess()` because this adjustment
2392+
/// may be for a write, in which case the setter's access might be used instead.
2393+
static AccessScope
2394+
getAccessScopeForFormalAccess(const ValueDecl *VD,
2395+
AccessLevel formalAccess,
2396+
const DeclContext *useDC,
2397+
bool treatUsableFromInlineAsPublic) {
2398+
AccessLevel access = getAdjustedFormalAccess(VD, formalAccess, useDC,
23782399
treatUsableFromInlineAsPublic);
2400+
const DeclContext *resultDC = VD->getDeclContext();
23792401

2380-
while (!result->isModuleScopeContext()) {
2381-
if (result->isLocalContext() || access == AccessLevel::Private)
2382-
return AccessScope(result, true);
2402+
while (!resultDC->isModuleScopeContext()) {
2403+
if (resultDC->isLocalContext() || access == AccessLevel::Private)
2404+
return AccessScope(resultDC, /*private*/true);
23832405

2384-
if (auto enclosingNominal = dyn_cast<NominalTypeDecl>(result)) {
2406+
if (auto enclosingNominal = dyn_cast<NominalTypeDecl>(resultDC)) {
23852407
auto enclosingAccess =
2386-
getAdjustedFormalAccess(enclosingNominal, useDC,
2387-
treatUsableFromInlineAsPublic);
2408+
getAdjustedFormalAccess(enclosingNominal, useDC,
2409+
treatUsableFromInlineAsPublic);
23882410
access = std::min(access, enclosingAccess);
23892411

2390-
} else if (auto enclosingExt = dyn_cast<ExtensionDecl>(result)) {
2412+
} else if (auto enclosingExt = dyn_cast<ExtensionDecl>(resultDC)) {
23912413
// Just check the base type. If it's a constrained extension, Sema should
23922414
// have already enforced access more strictly.
23932415
if (auto extendedTy = enclosingExt->getExtendedType()) {
23942416
if (auto nominal = extendedTy->getAnyNominal()) {
23952417
auto nominalAccess =
2396-
getAdjustedFormalAccess(nominal, useDC,
2397-
treatUsableFromInlineAsPublic);
2418+
getAdjustedFormalAccess(nominal, useDC,
2419+
treatUsableFromInlineAsPublic);
23982420
access = std::min(access, nominalAccess);
23992421
}
24002422
}
@@ -2403,16 +2425,16 @@ ValueDecl::getFormalAccessScope(const DeclContext *useDC,
24032425
llvm_unreachable("unknown DeclContext kind");
24042426
}
24052427

2406-
result = result->getParent();
2428+
resultDC = resultDC->getParent();
24072429
}
24082430

24092431
switch (access) {
24102432
case AccessLevel::Private:
24112433
case AccessLevel::FilePrivate:
2412-
assert(result->isModuleScopeContext());
2413-
return AccessScope(result, access == AccessLevel::Private);
2434+
assert(resultDC->isModuleScopeContext());
2435+
return AccessScope(resultDC, access == AccessLevel::Private);
24142436
case AccessLevel::Internal:
2415-
return AccessScope(result->getParentModule());
2437+
return AccessScope(resultDC->getParentModule());
24162438
case AccessLevel::Public:
24172439
case AccessLevel::Open:
24182440
return AccessScope::getPublic();
@@ -2421,6 +2443,128 @@ ValueDecl::getFormalAccessScope(const DeclContext *useDC,
24212443
llvm_unreachable("unknown access level");
24222444
}
24232445

2446+
AccessScope
2447+
ValueDecl::getFormalAccessScope(const DeclContext *useDC,
2448+
bool treatUsableFromInlineAsPublic) const {
2449+
return getAccessScopeForFormalAccess(this, getFormalAccess(), useDC,
2450+
treatUsableFromInlineAsPublic);
2451+
}
2452+
2453+
/// Checks if \p VD may be used from \p useDC, taking \@testable imports into
2454+
/// account.
2455+
///
2456+
/// Whenever the enclosing context of \p VD is usable from \p useDC, this
2457+
/// should compute the same result as checkAccess, below, but more slowly.
2458+
///
2459+
/// See ValueDecl::isAccessibleFrom for a description of \p forConformance.
2460+
static bool checkAccessUsingAccessScopes(const DeclContext *useDC,
2461+
const ValueDecl *VD,
2462+
AccessLevel access) {
2463+
AccessScope accessScope =
2464+
getAccessScopeForFormalAccess(VD, access, useDC,
2465+
/*treatUsableFromInlineAsPublic*/false);
2466+
return accessScope.getDeclContext() == useDC ||
2467+
AccessScope(useDC).isChildOf(accessScope);
2468+
}
2469+
2470+
/// Checks if \p VD may be used from \p useDC, taking \@testable imports into
2471+
/// account.
2472+
///
2473+
/// When \p access is the same as `VD->getFormalAccess()` and the enclosing
2474+
/// context of \p VD is usable from \p useDC, this ought to be the same as
2475+
/// getting the AccessScope for `VD` and checking if \p useDC is within it.
2476+
/// However, there's a source compatibility hack around protocol extensions
2477+
/// that makes it not quite the same.
2478+
///
2479+
/// See ValueDecl::isAccessibleFrom for a description of \p forConformance.
2480+
static bool checkAccess(const DeclContext *useDC, const ValueDecl *VD,
2481+
AccessLevel access, bool forConformance) {
2482+
auto *sourceDC = VD->getDeclContext();
2483+
2484+
if (!forConformance) {
2485+
if (auto *proto = sourceDC->getAsProtocolOrProtocolExtensionContext()) {
2486+
// FIXME: Swift 4.1 allowed accessing protocol extension methods that were
2487+
// marked 'public' if the protocol was '@_versioned' (now
2488+
// '@usableFromInline'). Which works at the ABI level, so let's keep
2489+
// supporting that here by explicitly checking for it.
2490+
if (access == AccessLevel::Public) {
2491+
assert(proto->getDeclContext()->isModuleScopeContext() &&
2492+
"if we get nested protocols, this should not apply to them");
2493+
if (proto->getFormalAccess() == AccessLevel::Internal &&
2494+
proto->isUsableFromInline()) {
2495+
return true;
2496+
}
2497+
}
2498+
2499+
// Skip the fast path below and just compare access scopes.
2500+
return checkAccessUsingAccessScopes(useDC, VD, access);
2501+
}
2502+
}
2503+
2504+
// Fast path: assume that the client context already has access to our parent
2505+
// DeclContext, and only check what might be different about this declaration.
2506+
if (!useDC)
2507+
return access >= AccessLevel::Public;
2508+
2509+
switch (access) {
2510+
case AccessLevel::Private:
2511+
return (useDC == sourceDC ||
2512+
AccessScope::allowsPrivateAccess(useDC, sourceDC));
2513+
case AccessLevel::FilePrivate:
2514+
return useDC->getModuleScopeContext() == sourceDC->getModuleScopeContext();
2515+
case AccessLevel::Internal: {
2516+
const ModuleDecl *sourceModule = sourceDC->getParentModule();
2517+
const DeclContext *useFile = useDC->getModuleScopeContext();
2518+
if (useFile->getParentModule() == sourceModule)
2519+
return true;
2520+
if (auto *useSF = dyn_cast<SourceFile>(useFile))
2521+
if (useSF->hasTestableImport(sourceModule))
2522+
return true;
2523+
return false;
2524+
}
2525+
case AccessLevel::Public:
2526+
case AccessLevel::Open:
2527+
return true;
2528+
}
2529+
llvm_unreachable("bad access level");
2530+
}
2531+
2532+
bool ValueDecl::isAccessibleFrom(const DeclContext *useDC,
2533+
bool forConformance) const {
2534+
auto access = getFormalAccess();
2535+
bool result = checkAccess(useDC, this, access, forConformance);
2536+
2537+
// For everything outside of protocols and operators, we should get the same
2538+
// result using either implementation of checkAccess, because useDC must
2539+
// already have access to this declaration's DeclContext.
2540+
// FIXME: Arguably, we're doing the wrong thing for operators here too,
2541+
// because we're finding internal operators within private types. Fortunately
2542+
// we have a requirement that a member operator take the enclosing type as an
2543+
// argument, so it won't ever match.
2544+
assert(getDeclContext()->getAsProtocolOrProtocolExtensionContext() ||
2545+
isOperator() ||
2546+
result == checkAccessUsingAccessScopes(useDC, this, access));
2547+
2548+
return result;
2549+
}
2550+
2551+
bool AbstractStorageDecl::isSetterAccessibleFrom(const DeclContext *DC,
2552+
bool forConformance) const {
2553+
assert(isSettable(DC));
2554+
2555+
// If a stored property does not have a setter, it is still settable from the
2556+
// designated initializer constructor. In this case, don't check setter
2557+
// access; it is not set.
2558+
if (hasStorage() && !isSettable(nullptr))
2559+
return true;
2560+
2561+
if (isa<ParamDecl>(this))
2562+
return true;
2563+
2564+
auto access = getSetterFormalAccess();
2565+
return checkAccess(DC, this, access, forConformance);
2566+
}
2567+
24242568
void ValueDecl::copyFormalAccessFrom(const ValueDecl *source,
24252569
bool sourceIsParentContext) {
24262570
if (!hasAccess()) {

lib/AST/NameLookup.cpp

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,83 +1520,6 @@ void ClassDecl::recordObjCMethod(AbstractFunctionDecl *method) {
15201520
vec.push_back(method);
15211521
}
15221522

1523-
static AccessLevel
1524-
adjustAccessLevelForProtocolExtension(const ValueDecl *VD, AccessLevel access) {
1525-
if (auto *ext = dyn_cast<ExtensionDecl>(VD->getDeclContext())) {
1526-
if (auto *protocol = ext->getAsProtocolOrProtocolExtensionContext()) {
1527-
// Note: it gets worse. The standard library has public methods
1528-
// in protocol extensions of a @usableFromInline internal protocol,
1529-
// and expects these extension methods to witness public protocol
1530-
// requirements. Which works at the ABI level, so let's keep
1531-
// supporting that here by passing 'isUsageFromInline'.
1532-
auto protoAccess =
1533-
protocol->getFormalAccessScope(/*useDC=*/nullptr,
1534-
/*isUsageFromInline=*/true);
1535-
// FIXME: The calling code should be written in terms of AccessScope,
1536-
// so that this can just use AccessScope::intersectWith.
1537-
access = std::min(access, protoAccess.requiredAccessForDiagnostics());
1538-
}
1539-
}
1540-
1541-
return access;
1542-
}
1543-
1544-
static bool checkAccess(const DeclContext *useDC, const ValueDecl *VD,
1545-
AccessLevel access, bool forConformance) {
1546-
if (!useDC)
1547-
return access >= AccessLevel::Public;
1548-
1549-
auto *sourceDC = VD->getDeclContext();
1550-
1551-
if (!forConformance)
1552-
access = adjustAccessLevelForProtocolExtension(VD, access);
1553-
1554-
switch (access) {
1555-
case AccessLevel::Private:
1556-
return (useDC == sourceDC ||
1557-
AccessScope::allowsPrivateAccess(useDC, sourceDC));
1558-
case AccessLevel::FilePrivate:
1559-
return useDC->getModuleScopeContext() == sourceDC->getModuleScopeContext();
1560-
case AccessLevel::Internal: {
1561-
const ModuleDecl *sourceModule = sourceDC->getParentModule();
1562-
const DeclContext *useFile = useDC->getModuleScopeContext();
1563-
if (useFile->getParentModule() == sourceModule)
1564-
return true;
1565-
if (auto *useSF = dyn_cast<SourceFile>(useFile))
1566-
if (useSF->hasTestableImport(sourceModule))
1567-
return true;
1568-
return false;
1569-
}
1570-
case AccessLevel::Public:
1571-
case AccessLevel::Open:
1572-
return true;
1573-
}
1574-
llvm_unreachable("bad access level");
1575-
}
1576-
1577-
bool ValueDecl::isAccessibleFrom(const DeclContext *DC,
1578-
bool forConformance) const {
1579-
auto access = getFormalAccess();
1580-
return checkAccess(DC, this, access, forConformance);
1581-
}
1582-
1583-
bool AbstractStorageDecl::isSetterAccessibleFrom(const DeclContext *DC,
1584-
bool forConformance) const {
1585-
assert(isSettable(DC));
1586-
1587-
// If a stored property does not have a setter, it is still settable from the
1588-
// designated initializer constructor. In this case, don't check setter
1589-
// access; it is not set.
1590-
if (hasStorage() && !isSettable(nullptr))
1591-
return true;
1592-
1593-
if (isa<ParamDecl>(this))
1594-
return true;
1595-
1596-
auto access = getSetterFormalAccess();
1597-
return checkAccess(DC, this, access, forConformance);
1598-
}
1599-
16001523
Type AbstractStorageDecl::getStorageInterfaceType() const {
16011524
if (auto var = dyn_cast<VarDecl>(this)) {
16021525
return var->getInterfaceType();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
public protocol PublicProtocol {
2+
}
3+
extension PublicProtocol {
4+
public func publicExtensionMethod() {}
5+
@usableFromInline internal func ufiExtensionMethod() {}
6+
internal func internalExtensionMethod() {}
7+
}
8+
9+
public struct PublicImpl: PublicProtocol {}
10+
11+
12+
@usableFromInline internal protocol UFIProtocol {
13+
}
14+
extension UFIProtocol {
15+
public func publicExtensionMethod() {}
16+
@usableFromInline internal func ufiExtensionMethod() {}
17+
internal func internalExtensionMethod() {}
18+
}
19+
20+
public struct UFIImpl: PublicProtocol {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -o %t/Lib.swiftmodule %S/Inputs/attr_usableFromInline_protocol_hole_helper.swift
3+
// RUN: %target-typecheck-verify-swift -I %t -verify-ignore-unknown
4+
5+
import Lib
6+
7+
func test(_ obj: PublicProtocol) {
8+
obj.publicExtensionMethod()
9+
obj.ufiExtensionMethod() // expected-error {{inaccessible}}
10+
obj.internalExtensionMethod() // expected-error {{inaccessible}}
11+
}
12+
13+
func test(_ obj: PublicImpl) {
14+
obj.publicExtensionMethod()
15+
obj.ufiExtensionMethod() // expected-error {{inaccessible}}
16+
obj.internalExtensionMethod() // expected-error {{inaccessible}}
17+
}
18+
19+
func test(_ obj: UFIImpl) {
20+
obj.publicExtensionMethod() // This being accessible is the "hole".
21+
obj.ufiExtensionMethod() // expected-error {{inaccessible}}
22+
obj.internalExtensionMethod() // expected-error {{inaccessible}}
23+
}

0 commit comments

Comments
 (0)