Skip to content

Limit ValueDecl::getFormalAccess and get rid of adjustAccessLevelForProtocolExtension #18048

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

Merged
merged 3 commits into from
Jul 25, 2018

Conversation

jrose-apple
Copy link
Contributor

Access scopes implement the correct language semantics, so make sure we're encouraging clients to use them. In the one case where we deliberately aren't doing so, make sure that that doesn't change semantics. Finally, in this push we can eliminate ValueDecl::adjustAccessLevelForProtocolExtension, though unfortunately not all of the underlying problems that originally made it necessary.

This net-added code but I promise it's a cleanup. Really.

@jrose-apple
Copy link
Contributor Author

@swift-ci Please test

@jrose-apple
Copy link
Contributor Author

@swift-ci Please test source compatibility

@jrose-apple
Copy link
Contributor Author

@swift-ci Please test compiler performance

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 8df9839c625e95345d1d9e03d756009a7ab6fe85

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 8df9839c625e95345d1d9e03d756009a7ab6fe85

@jrose-apple
Copy link
Contributor Author

@jrose-apple jrose-apple force-pushed the finding-ways-to-scope branch from 8df9839 to 56c4cfe Compare July 18, 2018 20:27
@jrose-apple
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 8df9839c625e95345d1d9e03d756009a7ab6fe85

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 8df9839c625e95345d1d9e03d756009a7ab6fe85

@swift-ci
Copy link
Contributor

Build comment file:

Summary for master full

Unexpected test results, excluded stats for RxSwift, CoreStore, ObjectMapper, ReactiveSwift

No regressions above thresholds

Debug-batch

debug-batch brief

Regressed (0)
name old new delta delta_pct
Improved (0)
name old new delta delta_pct
Unchanged (delta < 1.0% or delta < 100.0ms) (2)
name old new delta delta_pct
LLVM.NumLLVMBytesOutput 402,613,910 402,606,396 -7,514 -0.0%
time.swift-driver.wall 891.1s 889.2s -1.8s -0.2%

debug-batch detailed

Regressed (0)
name old new delta delta_pct
Improved (0)
name old new delta delta_pct
Unchanged (delta < 1.0% or delta < 100.0ms) (80)
name old new delta delta_pct
AST.NumASTBytesAllocated 10,868,004,026 10,868,322,884 318,858 0.0%
AST.NumDecls 25,116 25,116 0 0.0%
AST.NumDependencies 70,243 70,243 0 0.0%
AST.NumImportedExternalDefinitions 684,542 684,542 0 0.0%
AST.NumInfixOperators 10,476 10,476 0 0.0%
AST.NumLinkLibraries 0 0 0 0.0%
AST.NumLoadedModules 85,163 85,163 0 0.0%
AST.NumLocalTypeDecls 5 5 0 0.0%
AST.NumObjCMethods 9,778 9,778 0 0.0%
AST.NumPostfixOperators 14 14 0 0.0%
AST.NumPrecedenceGroups 5,353 5,353 0 0.0%
AST.NumPrefixOperators 53 53 0 0.0%
AST.NumReferencedDynamicNames 86 86 0 0.0%
AST.NumReferencedMemberNames 1,513,693 1,513,693 0 0.0%
AST.NumReferencedTopLevelNames 87,819 87,819 0 0.0%
AST.NumSourceBuffers 117,183 117,183 0 0.0%
AST.NumSourceLines 866,101 866,101 0 0.0%
AST.NumSourceLinesPerSecond 588,558 587,717 -841 -0.14%
AST.NumTotalClangImportedEntities 2,145,982 2,145,982 0 0.0%
AST.NumUsedConformances 72,182 72,164 -18 -0.02%
Driver.ChildrenMaxRSS 28,988,655,616 28,978,589,696 -10,065,920 -0.03%
Driver.DriverDepCascadingDynamic 0 0 0 0.0%
Driver.DriverDepCascadingExternal 0 0 0 0.0%
Driver.DriverDepCascadingMember 0 0 0 0.0%
Driver.DriverDepCascadingNominal 0 0 0 0.0%
Driver.DriverDepCascadingTopLevel 0 0 0 0.0%
Driver.DriverDepDynamic 0 0 0 0.0%
Driver.DriverDepExternal 0 0 0 0.0%
Driver.DriverDepMember 0 0 0 0.0%
Driver.DriverDepNominal 0 0 0 0.0%
Driver.DriverDepTopLevel 0 0 0 0.0%
Driver.NumDriverJobsRun 5,488 5,488 0 0.0%
Driver.NumDriverJobsSkipped 0 0 0 0.0%
Driver.NumDriverPipePolls 62,809 62,901 92 0.15%
Driver.NumDriverPipeReads 65,701 65,780 79 0.12%
Driver.NumProcessFailures 0 0 0 0.0%
Frontend.NumProcessFailures 0 0 0 0.0%
IRModule.NumIRAliases 6,345 6,345 0 0.0%
IRModule.NumIRBasicBlocks 1,316,444 1,316,410 -34 -0.0%
IRModule.NumIRComdatSymbols 0 0 0 0.0%
IRModule.NumIRFunctions 741,634 741,605 -29 -0.0%
IRModule.NumIRGlobals 964,605 964,594 -11 -0.0%
IRModule.NumIRIFuncs 0 0 0 0.0%
IRModule.NumIRInsts 15,999,363 15,998,866 -497 -0.0%
IRModule.NumIRNamedMetaData 27,340 27,340 0 0.0%
IRModule.NumIRValueSymbols 1,442,495 1,442,455 -40 -0.0%
LLVM.NumLLVMBytesOutput 402,613,910 402,606,396 -7,514 -0.0%
Parse.NumFunctionsParsed 46,112 46,112 0 0.0%
SILModule.NumSILGenDefaultWitnessTables 0 0 0 0.0%
SILModule.NumSILGenFunctions 816,337 816,332 -5 -0.0%
SILModule.NumSILGenGlobalVariables 12,027 12,027 0 0.0%
SILModule.NumSILGenVtables 3,232 3,232 0 0.0%
SILModule.NumSILGenWitnessTables 13,982 13,982 0 0.0%
SILModule.NumSILOptDefaultWitnessTables 0 0 0 0.0%
SILModule.NumSILOptFunctions 576,408 576,403 -5 -0.0%
SILModule.NumSILOptGlobalVariables 12,383 12,383 0 0.0%
SILModule.NumSILOptVtables 6,216 6,216 0 0.0%
SILModule.NumSILOptWitnessTables 27,600 27,600 0 0.0%
Sema.AccessLevelRequest 661,699 661,698 -1 -0.0%
Sema.DefaultAndMaxAccessLevelRequest 15,855 15,855 0 0.0%
Sema.EnumRawTypeRequest 5,699 5,699 0 0.0%
Sema.InheritedTypeRequest 26,088 26,088 0 0.0%
Sema.NamedLazyMemberLoadFailureCount 11,916 11,916 0 0.0%
Sema.NamedLazyMemberLoadSuccessCount 1,812,904 1,812,904 0 0.0%
Sema.NominalTypeLookupDirectCount 12,423,415 12,423,398 -17 -0.0%
Sema.NumConformancesDeserialized 1,936,551 1,936,539 -12 -0.0%
Sema.NumConstraintScopes 4,974,398 4,975,870 1,472 0.03%
Sema.NumConstraintsConsideredForEdgeContraction 11,628,019 11,629,282 1,263 0.01%
Sema.NumDeclsDeserialized 13,291,383 13,291,245 -138 -0.0%
Sema.NumDeclsValidated 971,425 971,424 -1 -0.0%
Sema.NumFunctionsTypechecked 402,453 402,453 0 0.0%
Sema.NumGenericSignatureBuilders 645,341 645,341 0 0.0%
Sema.NumLazyGenericEnvironments 2,469,856 2,469,820 -36 -0.0%
Sema.NumLazyGenericEnvironmentsLoaded 251,590 251,570 -20 -0.01%
Sema.NumLazyIterableDeclContexts 2,266,702 2,266,702 0 0.0%
Sema.NumTypesDeserialized 13,741,957 13,741,825 -132 -0.0%
Sema.NumTypesValidated 577,801 577,799 -2 -0.0%
Sema.NumUnloadedLazyIterableDeclContexts 1,536,955 1,536,961 6 0.0%
Sema.SetterAccessLevelRequest 42,519 42,519 0 0.0%
Sema.SuperclassTypeRequest 34,791 34,796 5 0.01%

Release

release brief

Regressed (0)
name old new delta delta_pct
Improved (0)
name old new delta delta_pct
Unchanged (delta < 1.0% or delta < 100.0ms) (2)
name old new delta delta_pct
LLVM.NumLLVMBytesOutput 376,888,878 376,862,908 -25,970 -0.01%
time.swift-driver.wall 1562.0s 1564.8s 2.7s 0.17%

release detailed

Regressed (0)
name old new delta delta_pct
Improved (0)
name old new delta delta_pct
Unchanged (delta < 1.0% or delta < 100.0ms) (23)
name old new delta delta_pct
AST.NumImportedExternalDefinitions 127,365 127,365 0 0.0%
AST.NumLoadedModules 5,259 5,259 0 0.0%
AST.NumTotalClangImportedEntities 398,191 398,191 0 0.0%
AST.NumUsedConformances 74,500 74,482 -18 -0.02%
IRModule.NumIRBasicBlocks 1,260,116 1,259,741 -375 -0.03%
IRModule.NumIRFunctions 574,753 574,729 -24 -0.0%
IRModule.NumIRGlobals 728,883 728,882 -1 -0.0%
IRModule.NumIRInsts 11,354,300 11,352,643 -1,657 -0.01%
IRModule.NumIRValueSymbols 1,161,023 1,160,998 -25 -0.0%
LLVM.NumLLVMBytesOutput 376,888,878 376,862,908 -25,970 -0.01%
SILModule.NumSILGenFunctions 267,137 267,124 -13 -0.0%
SILModule.NumSILOptFunctions 350,961 350,647 -314 -0.09%
Sema.NumConformancesDeserialized 858,490 857,228 -1,262 -0.15%
Sema.NumConstraintScopes 4,930,189 4,931,117 928 0.02%
Sema.NumDeclsDeserialized 2,434,496 2,433,931 -565 -0.02%
Sema.NumDeclsValidated 654,719 654,719 0 0.0%
Sema.NumFunctionsTypechecked 141,578 141,578 0 0.0%
Sema.NumGenericSignatureBuilders 99,633 99,630 -3 -0.0%
Sema.NumLazyGenericEnvironments 403,950 403,825 -125 -0.03%
Sema.NumLazyGenericEnvironmentsLoaded 47,646 47,631 -15 -0.03%
Sema.NumLazyIterableDeclContexts 266,455 266,410 -45 -0.02%
Sema.NumTypesDeserialized 2,971,140 2,969,010 -2,130 -0.07%
Sema.NumTypesValidated 179,447 179,447 0 0.0%

@jrose-apple
Copy link
Contributor Author

Cool, that extra assertion doesn't hurt build times.

@jrose-apple jrose-apple force-pushed the finding-ways-to-scope branch from 56c4cfe to dca5d46 Compare July 19, 2018 19:42
@jrose-apple
Copy link
Contributor Author

@swift-ci Please smoke test

Copy link
Contributor

@slavapestov slavapestov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay, I wanted to review this carefully because it’s pretty subtle and obviously we’ve got it wrong before. Most of the comments are optional, except for my concern about the behavioral change with usable from inlinable members of extensions of public protocols.

lib/AST/Decl.cpp Outdated

AccessScope accessScope =
getAccessScopeForFormalAccess(VD, access, useDC,
treatUsableFromInlineAsPublic);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the old behavior was that name lookup could find formally-public protocol members of extensions of usable from inline protocols. That is, we would adjust the internal protocol up to public if it was usable from inline, but not the extension member itself - it really has to be public.

This new check looks slightly different - if my understanding is correct you’re also applying the ‘usable from inline’ adjustment to the access level of the member itself. So this will make more things visible than were before.

I might be wrong but do you mind testing that a @usableFromInline member of an extension of a public protocol for example is not visible across a module boundary via name lookup?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and you are totally correct. That makes this a little more annoying to untangle, but it should be doable.

lib/AST/Decl.cpp Outdated
return checkAccessUsingAccessScopes(useDC, VD, access, forConformance);
}

switch (access) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment that the fast path relies on name lookup not visiting a type that is not visible to the caller, which is true for superclasses (you can’t have a public subclass of an internal class!) but not true for protocols (private conformances).

... but maybe we don’t need the fast path after all. If you think about it, computing an access scope isn’t that hard — in 90% of cases you’re just visiting a member of a type and then the type itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's at the call site, but it's probably worth including here too.

Access scope computation always walks the full DeclContext chain unless something is local, because it's recursive. It is something we could consider caching, though.

if (hasStorage() && !isSettable(nullptr))
return true;

if (isa<ParamDecl>(this))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you didn’t add this part, but I’m wondering if a fast path for ParamDecl makes sense in isAccessibleFrom() too. Actually, an even better fast path check might be this->getDeclContext()->isLocalContext() - if you find a definition that’s immediately in a local context, it must be via unqualified lookup, and it must be visible to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given how much trouble this has caused us I'm hesitant to include any new fast paths unless I can figure out how to write assertions for them.

///
/// \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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: split this into two functions, adjustAccessForUsableFomInline() and adjustAccessForTestableImport(). This might make the code clearer. Also the testable check is potentially expensive (you’re walking up DCs and then iterating over all imports) so it might be beneficial to only do it once, at the end of getFormalAccessScope().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I experimented with the split but I don't feel great about it: there are only a few call sites that pass a constant for one of the arguments, and if we ever add another kind of adjustment it will be harder to find them all. But it would save on repeatedly checking for testable imports…

I'm going to leave it out of this PR, anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that having 2 separate forms of adjustment (or more in the future) might make Slava's suggestion awkward; but can I add a vote here for a slightly more descriptive name than "adjusted"? It's not clear from the name that said adjustments are either comprehensive, desirable or idempotent. Maybe it'd be enough to say "getFullyAdjustedFormalAccess"? or "getCompletedFormalAccess" (and renaming "getFormalAccess" to "getIncompleteFormalAccess")? I'm not sure what a clearer term would be here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of those are more descriptive either. I don't have a better name because it's really just "adjust the formal access based on the context provided by these unrelated parameters". It's at least a static helper and not relevant outside of implementing these APIs on ValueDecl.

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

if (auto enclosingNominal = dyn_cast<NominalTypeDecl>(resultDC)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: micro-optimize these checks by changing the while into an infinite loo and only switch over the DeclContext kind once. Check for access == Private first since its cheap.

Copy link
Contributor Author

@jrose-apple jrose-apple Jul 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect all of that to be inlined anyway. I think it's fine.

superclassCtor->getFormalAccessScope(/*useDC*/nullptr,
/*usableFromInlineAsPublic=*/true);

if (superclassInliningAccessScope.isPublic()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the superclass is public and has an inlinable initializer, and you define a private subclass? It looks like we will still inherit the attribute and probably diagnose it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't change that part from what was there before, so…not a regression? :-) It doesn't seem to diagnose, but I can make sure we have a test case for it.

@jrose-apple
Copy link
Contributor Author

Thanks for all the comments! And very likely catching some real issues. I'll take a look today.

...to push people towards getFormalAccessScope. The one use case that
isn't covered by that is checking whether a declaration behaves as
'open' in the current file; I've added ValueDecl::hasOpenAccess to
handle that specific case.

No intended functionality change.
This gets adjustAccessLevelForProtocolExtension, a hack of sorts to
begin with, out of ValueDecl's general API, and down to a helper for
isAccessibleFrom and isSetterAccessibleFrom. (The only reason these
two don't go through access scopes is as an optimization.)
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.
@jrose-apple jrose-apple force-pushed the finding-ways-to-scope branch from dca5d46 to cd22c5d Compare July 23, 2018 23:36
@jrose-apple
Copy link
Contributor Author

@swift-ci Please test

@jrose-apple
Copy link
Contributor Author

@swift-ci Please test source compatibility

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 56c4cfed107fb1c1de938e3a5c78466e3855b729

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 56c4cfed107fb1c1de938e3a5c78466e3855b729

// '@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() &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We diagnose nested protocols in typeCheckDecl() but nothing prevents you from doing name lookup into them, and various practicalswift test cases exercise this (also see test/decl/nested/protocol.swift), so I think this still has to work (but it's OK if it doesn't return a correct result).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, hm. I'll write a test for that, then (in a follow-up PR).

@jrose-apple jrose-apple merged commit 2e45011 into swiftlang:master Jul 25, 2018
@jrose-apple jrose-apple deleted the finding-ways-to-scope branch July 25, 2018 23:43
Copy link
Contributor

@graydon graydon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to have to defer to Slava on most of this; I don't have enough of a mental model of formal access vs. access scopes, either in language semantics or complier implementation, to know whether this is ok. The bits I can understand look like simple refactors, so they're fine. But there's clearly more going on here!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants