Skip to content

Fix SE-0025 edge case involving public member of protocol extension of private protocol #15735

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 1 commit into from
Apr 6, 2018

Conversation

slavapestov
Copy link
Contributor

@slavapestov slavapestov commented Apr 4, 2018

The witness table for a conformance to a private protocol is private. For this reason any member of a protocol extension of such a protocol should also be private, since it expects to receive a witness table and therefore there's no way to call it from the outside.

However, the type checker was incorrectly allowing such members to be found by name lookup if a public type conformed to the private protocol. Calling such a member resulted in a linker error.

The type checker also allowed these members to witness protocol requirements of public protocols. Since we cannot break source compatibility, this behavior is preserved. When calling the protocol requirement from outside of the module with the concrete base type, we used to fail with a linker error; now we call the requirement virtually using a witness_method instruction.

Fixes rdar://problem/21380336, rdar://problem/29044612, let's go search JIRA for dupes... https://bugs.swift.org/browse/SR-2315, https://bugs.swift.org/browse/SR-2571, https://bugs.swift.org/browse/SR-2925, https://bugs.swift.org/browse/SR-3312, https://bugs.swift.org/browse/SR-6325, damn...

@slavapestov
Copy link
Contributor Author

@swift-ci Please smoke test

@slavapestov
Copy link
Contributor Author

@jrose-apple You might to take a look at this PR.

@gottesmm If you feel like it, take a look at the SILOptimizer change. This was a latent bug exposed by my changes.

cast<SingleValueInstruction>(AI)->replaceAllUsesWith(NewInstPair.first);

DeadApplies.push_back(AI);
replaceDeadApply(Apply, NewInstPair.first);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not code that I am that familiar with. But are you 200% sure that you are not going to run into any problems around iterator invalidation and or removing values still with uses. That is the reason why people usually follow this pattern.

Copy link
Contributor Author

@slavapestov slavapestov Apr 5, 2018

Choose a reason for hiding this comment

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

Other parts of the optimizer use replaceDeadApply(), so if that was wrong they would be wrong too. Note that we don't have any active iterators in this loop. We're iterating over a vector of SILInstructions.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not saying that using replaceDeadArray is necessarily wrong. What I am saying is that you are changing the code in a functional way by moving the time the applies are deleted from after the transformation to during the transformation. I am trying to prove to myself that doing so is safe (the pattern you are removing is a common defensive coding pattern in optimization passes). When I look at the git history these lines were added in the initial pass. @rudkx do you remember why you did this 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.

We'll see if the source compat suite passes :)

Copy link
Contributor

Choose a reason for hiding this comment

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

That doesn't necessarily mean anything.

@@ -30,3 +30,18 @@ public class PublicClass {
}
}

public protocol Thrower {
Copy link
Contributor

Choose a reason for hiding this comment

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

This test needs FileCheck patterns.

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 has them, see the function caller()?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know what you are talking about. I see caller on line 45 without a FileCheck pattern showing that the callee was or was not devirtualized. Or am I misreading the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, fixed

@@ -30,3 +30,18 @@ public class PublicClass {
}
}

public protocol Thrower {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know what you are talking about. I see caller on line 45 without a FileCheck pattern showing that the callee was or was not devirtualized. Or am I misreading the test?

cast<SingleValueInstruction>(AI)->replaceAllUsesWith(NewInstPair.first);

DeadApplies.push_back(AI);
replaceDeadApply(Apply, NewInstPair.first);
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not saying that using replaceDeadArray is necessarily wrong. What I am saying is that you are changing the code in a functional way by moving the time the applies are deleted from after the transformation to during the transformation. I am trying to prove to myself that doing so is safe (the pattern you are removing is a common defensive coding pattern in optimization passes). When I look at the git history these lines were added in the initial pass. @rudkx do you remember why you did this here?


replaceDeadApply(InnerAI, NewInst);
replaceDeadApply(InnerAI, NewInstPair.first);

auto newApplyAI = NewInstPair.second.getInstruction();
assert(newApplyAI && "devirtualized but removed apply site?");
I = newApplyAI->getIterator();
auto NewAI = FullApplySite::isa(newApplyAI);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just eliminate NewAI, i.e.:

return {FullApplySite::isa(newApplyAI), I};

Also next time, it would be helpful for reviewers who read the mailing list if you could put stuff like this in a separate focused PR. The reason why I am asking is that logically has nothing to do with the title/subject of the PR, then people who just look at Titles on the mailing list may not know that this is code that they should review.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And yes logically the SIL optimizer changes are distinct, and could be in a separate PR, but they were motivated by other changes in this PR. It can be tricky to find the right balance here. Sometimes, splitting up a change into too many PRs can also be difficult to review because its hard to get the big picture.

Copy link
Contributor

Choose a reason for hiding this comment

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

That is why in the PR you specifically tell the reviewer the big picture and uses references in between the PRs/etc to provide the connection for posterity. NOTE I am not saying that I have always done this well.

@slavapestov
Copy link
Contributor Author

@swift-ci Please smoke test

@slavapestov
Copy link
Contributor Author

@swift-ci Please test source compatibility

@slavapestov
Copy link
Contributor Author

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented Apr 5, 2018

Build failed
Swift Test OS X Platform
Git Sha - 2fac858

@swift-ci
Copy link
Contributor

swift-ci commented Apr 5, 2018

Build failed
Swift Test Linux Platform
Git Sha - 2fac858

@slavapestov
Copy link
Contributor Author

@gottesmm I forgot to push revised patches. I'll do after the source compat suite completes.

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

I only reviewed the AST/Sema parts, but I'm pretty unhappy with how many extra hacks there are here. I'd still like to pursue the "formally change the language to not require access" option that would not require special casing protocol extension scopes nearly as much.

I suggest adding a cross-module execution test case that shows the tricky linker cases, including using the protocol in a generic context from the client module with optimizations enabled (to make sure we don't devirtualize).

/// containing scope is not really visible. However, if the containing scope
/// is a protocol extension, they can show up here, so we have to filter
/// them out directly.
AccessLevel adjustAccessLevelForProtocolExtension(AccessLevel access) const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: this should be static.

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 calls getDeclContext() on this

Copy link
Contributor

Choose a reason for hiding this comment

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

Uh…that seems fishy, then. Why isn't it only called when needed?

getFormalAccessScope(const DeclContext *useDC = nullptr,
bool respectVersionedAttr = false) const;
getFormalAccessScope(const DeclContext *useDC=nullptr,
bool isUsageFromInline=false) const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: Why remove the spaces?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also this name isn't really correct. The formal access scope doesn't change when used from an inline function; it changes when it's only being used as a symbol and not as part of API.

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 renamed this in the other PR that got merged already.

@@ -2319,7 +2326,8 @@ class ValueDecl : public Decl {
/// A public declaration is accessible everywhere.
///
/// If \p DC is null, returns true only if this declaration is public.
bool isAccessibleFrom(const DeclContext *DC) const;
bool isAccessibleFrom(const DeclContext *DC,
bool forConformance=false) const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please include an explanation of the forConformance flag in the doc comment. This whole thing was really subtle and deserves to be written down.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

A protocol extension of a private protocol can define internal
or public members. We should not be able to find these members
from another file or module if an internal or public type
conforms to the protocol.

Fixes <rdar://problem/21380336>.
@slavapestov
Copy link
Contributor Author

@jrose-apple I added some more comments, cleaned it up a bit and added the SIL optimizer test case you requested.

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

Thanks. I still don't think this is the right answer for the language, but it's a non-regressing answer. Can you file a Radar to track one of us looking into changing the witness rule?

@slavapestov
Copy link
Contributor Author

Sure thing.

@slavapestov
Copy link
Contributor Author

@swift-ci Please smoke test

1 similar comment
@slavapestov
Copy link
Contributor Author

@swift-ci Please smoke test

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