Skip to content

[WIP] Check the containing context when checking access for operators #18164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 17 additions & 24 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2347,33 +2347,17 @@ class ValueDecl : public Decl {
/// Returns the access level specified explicitly by the user, or provided by
/// default according to language rules.
///
/// This is the access used when calculating if access control is being used
/// consistently. If \p useDC is provided (the location where the value is
/// being used), features that affect formal access such as \c \@testable are
/// taken into account.
///
/// If \p treatUsableFromInlineAsPublic is true, declarations marked with the
/// \c @usableFromInline attribute are treated as public. This is normally
/// false for name lookup and other source language concerns, but true when
/// computing the linkage of generated functions.
/// Most of the time this is not the interesting value to check; access is
/// limited by enclosing scopes per SE-0025. Use #getFormalAccessScope to
/// check if access control is being used consistently, and to take features
/// such as \c \@testable and \c \@usableFromInline into account.
///
/// \sa getFormalAccessScope
AccessLevel getFormalAccess(const DeclContext *useDC = nullptr,
bool treatUsableFromInlineAsPublic = false) const;

/// If this declaration is a member of a protocol extension, return the
/// minimum of the given access level and the protocol's access level.
///
/// Otherwise, return the given access level unmodified.
///
/// This is used when checking name lookup visibility. Protocol extension
/// members can be found when performing name lookup on a concrete type;
/// if the concrete type is visible from the lookup context but the
/// protocol is not, we do not want the protocol extension members to be
/// visible.
AccessLevel adjustAccessLevelForProtocolExtension(AccessLevel access) const;
/// \sa hasOpenAccess
AccessLevel getFormalAccess() const;

/// Determine whether this Decl has either Private or FilePrivate access.
/// Determine whether this Decl has either Private or FilePrivate access,
/// and its DeclContext does not.
bool isOutermostPrivateOrFilePrivateScope() const;

/// Returns the outermost DeclContext from which this declaration can be
Expand All @@ -2395,6 +2379,7 @@ class ValueDecl : public Decl {
///
/// \sa getFormalAccess
/// \sa isAccessibleFrom
/// \sa hasOpenAccess
AccessScope
getFormalAccessScope(const DeclContext *useDC = nullptr,
bool treatUsableFromInlineAsPublic = false) const;
Expand Down Expand Up @@ -2448,6 +2433,14 @@ class ValueDecl : public Decl {
bool isAccessibleFrom(const DeclContext *DC,
bool forConformance = false) const;

/// Returns whether this declaration should be treated as \c open from
/// \p useDC. This is very similar to #getFormalAccess, but takes
/// \c \@testable into account.
///
/// This is mostly only useful when considering requirements on an override:
/// if the base declaration is \c open, the override might have to be too.
bool hasOpenAccess(const DeclContext *useDC) const;

/// Retrieve the "interface" type of this value, which uses
/// GenericTypeParamType if the declaration is generic. For a generic
/// function, this will have a GenericFunctionType with a
Expand Down
235 changes: 200 additions & 35 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2280,10 +2280,44 @@ static AccessLevel getTestableAccess(const ValueDecl *decl) {
return AccessLevel::Public;
}

/// Adjust \p access based on whether \p VD is \@usableFromInline or has been
/// testably imported from \p useDC.
///
/// \p access isn't always just `VD->getFormalAccess()` because this adjustment
/// may be for a write, in which case the setter's access might be used instead.
static AccessLevel getAdjustedFormalAccess(const ValueDecl *VD,
AccessLevel access,
const DeclContext *useDC,
bool treatUsableFromInlineAsPublic) {
if (treatUsableFromInlineAsPublic &&
access == AccessLevel::Internal &&
VD->isUsableFromInline()) {
return AccessLevel::Public;
}

if (useDC && (access == AccessLevel::Internal ||
access == AccessLevel::Public)) {
if (auto *useSF = dyn_cast<SourceFile>(useDC->getModuleScopeContext()))
if (useSF->hasTestableImport(VD->getModuleContext()))
return getTestableAccess(VD);
}

return access;
}

/// Convenience overload that uses `VD->getFormalAccess()` as the access to
/// adjust.
static AccessLevel
getAdjustedFormalAccess(const ValueDecl *VD, const DeclContext *useDC,
bool treatUsableFromInlineAsPublic) {
return getAdjustedFormalAccess(VD, VD->getFormalAccess(), useDC,
treatUsableFromInlineAsPublic);
}

AccessLevel ValueDecl::getEffectiveAccess() const {
auto effectiveAccess =
getFormalAccess(/*useDC=*/nullptr,
/*treatUsableFromInlineAsPublic=*/true);
getAdjustedFormalAccess(this, /*useDC=*/nullptr,
/*treatUsableFromInlineAsPublic=*/true);

// Handle @testable.
switch (effectiveAccess) {
Expand Down Expand Up @@ -2335,48 +2369,54 @@ AccessLevel ValueDecl::getEffectiveAccess() const {
return effectiveAccess;
}

AccessLevel ValueDecl::getFormalAccess(const DeclContext *useDC,
bool treatUsableFromInlineAsPublic) const {
AccessLevel ValueDecl::getFormalAccess() const {
ASTContext &ctx = getASTContext();
AccessLevel result = ctx.evaluator(AccessLevelRequest{const_cast<ValueDecl *>(this)});
if (treatUsableFromInlineAsPublic &&
result == AccessLevel::Internal &&
isUsableFromInline()) {
return AccessLevel::Public;
}
if (useDC && (result == AccessLevel::Internal ||
result == AccessLevel::Public)) {
if (auto *useSF = dyn_cast<SourceFile>(useDC->getModuleScopeContext()))
if (useSF->hasTestableImport(getModuleContext()))
return getTestableAccess(this);
}
return result;
return ctx.evaluator(AccessLevelRequest{const_cast<ValueDecl *>(this)});
}

AccessScope
ValueDecl::getFormalAccessScope(const DeclContext *useDC,
bool treatUsableFromInlineAsPublic) const {
const DeclContext *result = getDeclContext();
AccessLevel access = getFormalAccess(useDC, treatUsableFromInlineAsPublic);
bool ValueDecl::hasOpenAccess(const DeclContext *useDC) const {
assert(isa<ClassDecl>(this) || isa<ConstructorDecl>(this) ||
isPotentiallyOverridable());

while (!result->isModuleScopeContext()) {
if (result->isLocalContext() || access == AccessLevel::Private)
return AccessScope(result, true);
AccessLevel access =
getAdjustedFormalAccess(this, useDC,
/*treatUsableFromInlineAsPublic*/false);
return access == AccessLevel::Open;
}

if (auto enclosingNominal = dyn_cast<NominalTypeDecl>(result)) {
/// Given the formal access level for using \p VD, compute the scope where
/// \p VD may be accessed, taking \@usableFromInline, \@testable imports,
/// and enclosing access levels into account.
///
/// \p access isn't always just `VD->getFormalAccess()` because this adjustment
/// may be for a write, in which case the setter's access might be used instead.
static AccessScope
getAccessScopeForFormalAccess(const ValueDecl *VD,
AccessLevel formalAccess,
const DeclContext *useDC,
bool treatUsableFromInlineAsPublic) {
AccessLevel access = getAdjustedFormalAccess(VD, formalAccess, useDC,
treatUsableFromInlineAsPublic);
const DeclContext *resultDC = VD->getDeclContext();

while (!resultDC->isModuleScopeContext()) {
if (resultDC->isLocalContext() || access == AccessLevel::Private)
return AccessScope(resultDC, /*private*/true);

if (auto enclosingNominal = dyn_cast<NominalTypeDecl>(resultDC)) {
auto enclosingAccess =
enclosingNominal->getFormalAccess(useDC,
treatUsableFromInlineAsPublic);
getAdjustedFormalAccess(enclosingNominal, useDC,
treatUsableFromInlineAsPublic);
access = std::min(access, enclosingAccess);

} else if (auto enclosingExt = dyn_cast<ExtensionDecl>(result)) {
} else if (auto enclosingExt = dyn_cast<ExtensionDecl>(resultDC)) {
// Just check the base type. If it's a constrained extension, Sema should
// have already enforced access more strictly.
if (auto extendedTy = enclosingExt->getExtendedType()) {
if (auto nominal = extendedTy->getAnyNominal()) {
auto nominalAccess =
nominal->getFormalAccess(useDC,
treatUsableFromInlineAsPublic);
getAdjustedFormalAccess(nominal, useDC,
treatUsableFromInlineAsPublic);
access = std::min(access, nominalAccess);
}
}
Expand All @@ -2385,16 +2425,16 @@ ValueDecl::getFormalAccessScope(const DeclContext *useDC,
llvm_unreachable("unknown DeclContext kind");
}

result = result->getParent();
resultDC = resultDC->getParent();
}

switch (access) {
case AccessLevel::Private:
case AccessLevel::FilePrivate:
assert(result->isModuleScopeContext());
return AccessScope(result, access == AccessLevel::Private);
assert(resultDC->isModuleScopeContext());
return AccessScope(resultDC, access == AccessLevel::Private);
case AccessLevel::Internal:
return AccessScope(result->getParentModule());
return AccessScope(resultDC->getParentModule());
case AccessLevel::Public:
case AccessLevel::Open:
return AccessScope::getPublic();
Expand All @@ -2403,6 +2443,131 @@ ValueDecl::getFormalAccessScope(const DeclContext *useDC,
llvm_unreachable("unknown access level");
}

AccessScope
ValueDecl::getFormalAccessScope(const DeclContext *useDC,
bool treatUsableFromInlineAsPublic) const {
return getAccessScopeForFormalAccess(this, getFormalAccess(), useDC,
treatUsableFromInlineAsPublic);
}

/// Checks if \p VD may be used from \p useDC, taking \@testable imports into
/// account.
///
/// Whenever the enclosing context of \p VD is usable from \p useDC, this
/// should compute the same result as checkAccess, below, but more slowly.
///
/// See ValueDecl::isAccessibleFrom for a description of \p forConformance.
static bool checkAccessUsingAccessScopes(const DeclContext *useDC,
const ValueDecl *VD,
AccessLevel access) {
AccessScope accessScope =
getAccessScopeForFormalAccess(VD, access, useDC,
/*treatUsableFromInlineAsPublic*/false);
return accessScope.getDeclContext() == useDC ||
AccessScope(useDC).isChildOf(accessScope);
}

/// Checks if \p VD may be used from \p useDC, taking \@testable imports into
/// account.
///
/// When \p access is the same as `VD->getFormalAccess()` and the enclosing
/// context of \p VD is usable from \p useDC, this ought to be the same as
/// getting the AccessScope for `VD` and checking if \p useDC is within it.
/// However, there's a source compatibility hack around protocol extensions
/// that makes it not quite the same.
///
/// See ValueDecl::isAccessibleFrom for a description of \p forConformance.
static bool checkAccess(const DeclContext *useDC, const ValueDecl *VD,
AccessLevel access, bool forConformance) {
auto *sourceDC = VD->getDeclContext();
bool canUseFastPath = true;

if (!forConformance) {
if (auto *proto = sourceDC->getAsProtocolOrProtocolExtensionContext()) {
// FIXME: Swift 4.1 allowed accessing protocol extension methods that were
// marked 'public' if the protocol was '@_versioned' (now
// '@usableFromInline'). Which works at the ABI level, so let's keep
// supporting that here by explicitly checking for it.
if (access == AccessLevel::Public) {
assert(proto->getDeclContext()->isModuleScopeContext() &&
"if we get nested protocols, this should not apply to them");
if (proto->getFormalAccess() == AccessLevel::Internal &&
proto->isUsableFromInline()) {
return true;
}
}

canUseFastPath = false;
}

if (VD->isOperator())
canUseFastPath = false;
}

if (!canUseFastPath) {
// Skip the fast path below and just compare access scopes.
return checkAccessUsingAccessScopes(useDC, VD, access);
}

// Fast path: assume that the client context already has access to our parent
// DeclContext, and only check what might be different about this declaration.
if (!useDC)
return access >= AccessLevel::Public;

switch (access) {
case AccessLevel::Private:
return (useDC == sourceDC ||
AccessScope::allowsPrivateAccess(useDC, sourceDC));
case AccessLevel::FilePrivate:
return useDC->getModuleScopeContext() == sourceDC->getModuleScopeContext();
case AccessLevel::Internal: {
const ModuleDecl *sourceModule = sourceDC->getParentModule();
const DeclContext *useFile = useDC->getModuleScopeContext();
if (useFile->getParentModule() == sourceModule)
return true;
if (auto *useSF = dyn_cast<SourceFile>(useFile))
if (useSF->hasTestableImport(sourceModule))
return true;
return false;
}
case AccessLevel::Public:
case AccessLevel::Open:
return true;
}
llvm_unreachable("bad access level");
}

bool ValueDecl::isAccessibleFrom(const DeclContext *useDC,
bool forConformance) const {
auto access = getFormalAccess();
bool result = checkAccess(useDC, this, access, forConformance);

// For everything outside of protocols, we should get the same result using
// either implementation of checkAccess, because useDC must already have
// access to this declaration's DeclContext.
assert(getDeclContext()->getAsProtocolOrProtocolExtensionContext() ||
result == checkAccessUsingAccessScopes(useDC, this, access));

return result;
}

bool AbstractStorageDecl::isSetterAccessibleFrom(const DeclContext *DC,
bool forConformance) const {
assert(isSettable(DC));

// If a stored property does not have a setter, it is still settable from the
// designated initializer constructor. In this case, don't check setter
// access; it is not set.
if (hasStorage() && !isSettable(nullptr))
return true;

if (isa<ParamDecl>(this))
return true;

auto access = getSetterFormalAccess();
return checkAccess(DC, this, access, forConformance);
}

void ValueDecl::copyFormalAccessFrom(const ValueDecl *source,
bool sourceIsParentContext) {
if (!hasAccess()) {
Expand Down
Loading