-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Check that signatures of @usableFromInline declarations only reference @usableFromInline or public types #16586
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
Check that signatures of @usableFromInline declarations only reference @usableFromInline or public types #16586
Conversation
ac2d25b
to
a0c6ca0
Compare
@swift-ci Please test |
Build failed |
Build failed |
a0c6ca0
to
6f9dc2c
Compare
@swift-ci Please test |
1 similar comment
@swift-ci Please test |
@swift-ci Please test source compatibility |
1 similar comment
@swift-ci Please test source compatibility |
Build failed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whew, that was more than I thought it would be! I do have some concerns about this implementation, though.
@@ -1317,20 +1328,25 @@ ERROR(unsupported_nested_protocol,none, | |||
// Type aliases | |||
ERROR(type_alias_underlying_type_access,none, | |||
"type alias %select{must be declared %select{" | |||
"%select{private|fileprivate|internal|%error|%error}2|private or fileprivate}3" | |||
"%select{private|fileprivate|internal|%error|%error}1|private or fileprivate}3" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(here and below) This was correct the way it was before. Argument 1 is the current access; argument 2 is the required access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure? When isExplicit is true, %1 is the decl's getFormalAccess(), when isExplicit is false, %1 is the AccessScope's requiredAccessForDiagnostics()
. On the other hand, %2 is the AccessScope's accessLevelForDiagnostics()
.
I noticed some inconsistency here and cleaned it up. Some diagnostics were using %2 here for the isExplicit == false case, but this appeared to generate a wrong diagnostic because of the distinction between requiredAccessForDiagnostics()
and accessLevelForDiagnostics()
with private vs fileprivate. This change and tests for it was in a separate patch so it should be easier to review that way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the difference in messages: "must be declared X" vs. "cannot be declared Y". There might be mistakes at some of the use sites, but they're definitely not supposed to be the same value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"When isExplicit is true, %1 is the decl's getFormalAccess(), when isExplicit is false, %1 is the AccessScope's requiredAccessForDiagnostics()" -- so they're different values :)
So using %1 vs %2 in the !isExplicit case is almost equivalent, except for private vs fileprivate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see: you changed the call site too. Please don't do this; it muddles the notion of what each parameter is used for. At that point I'd rather just use different diagnostics.
lib/Sema/TypeCheckDecl.cpp
Outdated
TypeChecker::TypeAccessScopeCacheMap &Cache; | ||
|
||
protected: | ||
ASTContext &Context; | ||
Optional<AccessScope> Scope = AccessScope::getPublic(); | ||
|
||
AccessScopeChecker(const DeclContext *useDC, | ||
bool treatUsableFromInlineAsPublic, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're definitely starting to convince me that UsableFromInline is really a new access level.
lib/Sema/TypeCheckDecl.cpp
Outdated
AccessScope accessScope, | ||
AccessLevel contextAccess) { | ||
if (!params) | ||
return; | ||
|
||
if (checkUsableFromInline) { | ||
if (auto *VD = dyn_cast<ValueDecl>(owner)) { | ||
if (VD->getFormalAccess() != AccessLevel::Internal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a suspicious condition—this could be above internal
or it could be below internal
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checkUsableFromInline
means only check @usableFromInline and nothing else. So we call this function once with checkUsableFromInline=false
if the decl is not @usableFromInline; if it is, we call it twice, first with false
then with true
.
lib/Sema/TypeCheckDecl.cpp
Outdated
!isa<ModuleDecl>(accessScope.getDeclContext()) && | ||
TC.getLangOpts().isSwiftVersion3()) { | ||
downgradeToWarning = DowngradeToWarning::Yes; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happened to this code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a separate patch -- it was duplicated twice so I moved it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…and now it's applied to more places. Maybe it doesn't matter, though.
lib/Sema/TypeCheckDecl.cpp
Outdated
} | ||
|
||
auto minAccess = minAccessScope.accessLevelForDiagnostics(); | ||
|
||
bool isExplicit = | ||
owner->getAttrs().hasAttribute<AccessControlAttr>() || | ||
owner->getDeclContext()->getAsProtocolOrProtocolExtensionContext(); | ||
isa<ProtocolDecl>(owner->getDeclContext()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, good catch! Is there a test for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/Sema/TypeCheckDecl.cpp
Outdated
@@ -2175,14 +2223,16 @@ static void checkAccessControl(TypeChecker &TC, const Decl *D) { | |||
|
|||
case DeclKind::Struct: { | |||
auto SD = cast<StructDecl>(D); | |||
checkGenericParamAccess(TC, SD->getGenericParams(), SD); | |||
checkGenericParamAccess(TC, SD->getGenericParams(), SD, | |||
/*checkUsableFromInline=*/false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(here and elsewhere) Why is this false
and not inherited? Can you leave a comment to that effect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's correct. checkAccessControl()
only checks access control rules. The separate checkUsableFromInline()
performs the same visitation of all TypeLocs, calling checkTypeAccess() with checkUsableFromInline=true
.
lib/Sema/TypeCheckDecl.cpp
Outdated
if (anyVar->getFormalAccess() != AccessLevel::Internal) | ||
return; | ||
if (!anyVar->isUsableFromInline()) | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This "internal and usable from inline" check shows up a lot. Maybe worth a helper function, even though it's so simple?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I'm going to drop the assertion from isUsableFromInline() that the access level is internal, and just return false if its not.
lib/Sema/TypeCheckDecl.cpp
Outdated
}; | ||
|
||
std::for_each(assocType->getInherited().begin(), | ||
assocType->getInherited().end(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to check requirements, not just the inheritance clause, right? (Also, llvm::for_each
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. As you correctly noted this is copy and pasted from checkAccessControl() so probably the existing code was wrong too.
lib/Sema/TypeCheckDecl.cpp
Outdated
Type rawType = ED->getRawType(); | ||
auto rawTypeLocIter = std::find_if(ED->getInherited().begin(), | ||
ED->getInherited().end(), | ||
[&](TypeLoc inherited) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay this is really starting to seem like too much code duplicated from the regular access checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Perhaps we can factor out a visitor that walks the TypeLocs in a Decl?
lib/Sema/TypeCheckDecl.cpp
Outdated
@@ -4508,6 +4934,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> { | |||
|
|||
TC.checkDeclAttributes(SD); | |||
checkAccessControl(TC, SD); | |||
checkUsableFromInline(TC, SD); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to do both? Can't checkAccessControl
handle it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I could merge the two into one function. It would eliminate the duplicated logic for visting the TypeLocs, at the cost of making the whole function longer. However, we could then split it up into smaller functions for each Decl kind or something, maybe even turn it into a visitor. Sounds good?
Source compat failure:
Looks like I need to disable the new checks in |
Less than 5, please. We don't currently have any language changes that are gated on 4.2 besides SDK changes, right? |
14ed713
to
a68baa4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments for everything except the last Sema change.
lib/Sema/TypeCheckDecl.cpp
Outdated
@@ -2419,7 +2410,7 @@ static void checkAccessControl(TypeChecker &TC, const Decl *D) { | |||
if (!EED->hasAssociatedValues()) | |||
return; | |||
for (auto &P : *EED->getParameterList()) { | |||
checkTypeAccess(TC, P->getTypeLoc(), P, | |||
checkTypeAccess(TC, P->getTypeLoc(), EED->getParentEnum(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one seems wrong; it should just be EED
, right? (Because some day we might have enum cases less visible than the enum.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. Fixed
@@ -1317,20 +1328,25 @@ ERROR(unsupported_nested_protocol,none, | |||
// Type aliases | |||
ERROR(type_alias_underlying_type_access,none, | |||
"type alias %select{must be declared %select{" | |||
"%select{private|fileprivate|internal|%error|%error}2|private or fileprivate}3" | |||
"%select{private|fileprivate|internal|%error|%error}1|private or fileprivate}3" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see: you changed the call site too. Please don't do this; it muddles the notion of what each parameter is used for. At that point I'd rather just use different diagnostics.
lib/Sema/TypeCheckDecl.cpp
Outdated
!isa<ModuleDecl>(accessScope.getDeclContext()) && | ||
TC.getLangOpts().isSwiftVersion3()) { | ||
downgradeToWarning = DowngradeToWarning::Yes; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…and now it's applied to more places. Maybe it doesn't matter, though.
lib/AST/Decl.cpp
Outdated
@@ -2082,7 +2082,8 @@ SourceLoc ValueDecl::getAttributeInsertionLoc(bool forModifier) const { | |||
/// Returns true if \p VD needs to be treated as publicly-accessible | |||
/// at the SIL, LLVM, and machine levels due to being @usableFromInline. | |||
bool ValueDecl::isUsableFromInline() const { | |||
assert(getFormalAccess() == AccessLevel::Internal); | |||
if (getFormalAccess() != AccessLevel::Internal) | |||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unhappy with this. If you're going to make this an API that can be called everywhere, shouldn't it return true for open
and public
decls as well?
a68baa4
to
eb43b62
Compare
Slava and I talked about my remaining concerns in person.
eb43b62
to
155c94f
Compare
155c94f
to
c241e38
Compare
Make this method public and fix it to do the right thing for associated types.
c241e38
to
384aad4
Compare
Previously we made sure that inlinable function bodies only reference @usableFromInline or public declarations, but we also have to make sure that @usableFromInline declarations only reference other declarations that are at least as visible. Otherwise, you could, for example, define a @usableFromInline typealias which referenced an internal type, and then reference the typealias from a @usableFromInline function body.
AccessControlCheckerBase |- AccessControlChecker |- UsableFromInlineChecker This factors out the checkUsableFromInline parameter that is passed around all over the place.
384aad4
to
9b43f26
Compare
@swift-ci Please test |
@swift-ci Please test source compatibility |
This has been open for far too long. Since the latest iteration moves a lot of code to a new source file, I'm going to merge it now to avoid more merge conflicts. I'm not quite done extracting out the common visitor, but if you squint you will be able to guess where it's going to go. |
We were performing these checks for references from the body of inlinable functions as part of availability checking, but we also have to do similar checks in access level checking for types that are referenced from the signature of the function itself.
Also, allow type aliases to be @usableFromInline, and ensure that only @usableFromInline type aliases are referenced from inlinable bodies.
This plugs some holes in the model that could cause linker errors on invalid code. Fixes rdar://problem/32722385, rdar://problem/39532028,