Skip to content
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

[SR-2925] Inconsistent linking in debug vs. release builds w/ public method in extension of internal protocol #45519

Closed
allevato opened this issue Oct 12, 2016 · 9 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself optimized only Flag: An issue whose reproduction requires optimized compilation

Comments

@allevato
Copy link
Collaborator

Previous ID SR-2925
Radar rdar://problem/21380336
Original Reporter @allevato
Type Bug
Status Resolved
Resolution Done

Attachment: Download

Environment

Apple Swift version 3.0 (swiftlang-800.0.46.2 clang-800.0.38)

Additional Detail from JIRA
Votes 2
Component/s Compiler
Labels Bug, OptimizedOnly
Assignee None
Priority Medium

md5: 82c10c0bdffe3fe1b4f8be7579763da4

is duplicated by:

  • SR-6433 Cannot declare public default protocol impls based on shadowed associated types
  • SR-6790 A public default implementation of a public superprotocol's requirement in an internal subprotocol is not exported in release builds resulting in a linker error

Issue Description:

Imagine the following scenario (see the attached ZIP for a small self-contained Swift PM example):

  • A module contains one or more concrete types that are public (let's call one ConcreteType).

  • The module also contains an internal protocol with some methods (let's call the protocol InternalProtocol). The concrete types are extended to conform to this protocol for use elsewhere within the module:

    extension ConcreteType: InternalProtocol {
      ...
    }
  • An extension of the protocol itself is made to provide default implementations of some of those methods. Even though the protocol itself is internal, one of these methods is declared public so that it will be visible externally. (In other words, we want the protocol to be invisible to external users, but we want to provide one of those methods to them as if it were defined on the concrete type extensions, to share its default implementation across all of the concrete types without duplication:

    extension InternalProtocol {
      public func publicBar() { ... }
    }
  • Another module imports the above module and tries to call the method described above (let c = ConcreteType(); c.publicBar()).

When building in debug mode, this compiles, links, and executes as expected:

$ swift build -c debug

Compile Swift Module 'Framework' (2 sources)
Compile Swift Module 'Client' (1 sources)
Linking ./.build/debug/Client

When building in release mode, compilation of all sources succeeds but a failure occurs at link time:

$ swift build -c release | xcrun swift-demangle

<unknown>:0: error: build had 1 command failures
error: exit(1): /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-build-tool -f /Users/allevato/Source/protocol-test/.build/release.yaml
Linking ./.build/release/Client
Undefined symbols for architecture x86_64:
  "_(extension in Framework):Framework.InternalProtocol.publicBar () -> ()", referenced from:
      _main in main.swift.o
     (maybe you meant: _generic specialization <Framework.ConcreteType with Framework.ConcreteType : Framework.InternalProtocol in Framework> of (extension in Framework):Framework.InternalProtocol.publicBar () -> ())
ld: symbol(s) not found for architecture x86_64

It appears that some symbol hiding/stripping that occurs in release builds is causing the extension method on the internal protocol to not be found, even though the method itself is public?

(Looking more closely, it's interesting that it's suggesting that I may have meant a generic specialization when there are no generics involved in this example...)

In any case, the difference in behavior between debug/release builds seems to be a bug. If I'm understanding what's going on correctly, one of two things should be happening:

1. This kind of visibility mixing should be allowed, and the public method on the internal protocol extension should be exposed for other modules to link to, or
2. This kind of visibility mixing should not be allowed, and the error should be emitted at compile time in both debug and release builds, rather than leading to harder-to-diagnose linker errors.

I don't have a deep enough understanding of the Swift compiler to know which of the above is correct, but my preference would be for #1, because it allows for some convenient selective member hiding while reducing code duplication. More real-world context can be found here <https://github.com/apple/swift-protobuf/issues/70\>; see also the pull request that fixes it for the diff of what had to be changed <https://github.com/apple/swift-protobuf/pull/71\>.

@belkadan
Copy link
Contributor

I thought we had locked down on this entirely by now, but reworking access control might have let some cases slip through again.

(It's not an unreasonable feature request either, but it's one that needs more work and probably some design.)

@swift-ci
Copy link
Collaborator

Comment by Thomas Catterall (JIRA)

Just encountered this using a framework exposing a public set of protocol conformances to an internal extension

```
internal MyProtocol {
var id: String
}

extension MyProtocol {
public var id: String { return "default" }
}
```

The framework always builds fine, but in release builds the app target that imports the framework fails to find the protocol witness table or the extension. I assume this is something to do with optimisations. Is the bug described in this comment the same as this ticket or shall I create a new one?

Swift 3, Xcode 8.1 8B62 (App Store)

@allevato
Copy link
Collaborator Author

Yes, that looks like a manifestation of the same bug.

@belkadan
Copy link
Contributor

To be clear, this is not supported at the moment. It's more than just something being marked with the "wrong" visibility; it's about not exposing details of MyProtocol to other modules.

@allevato
Copy link
Collaborator Author

Going forward, does that mean that the fix for this bug is one that will prevent these examples from compiling at all, and that we should write a Swift evolution proposal to design this properly once additive proposals are ready to be accepted again?

@keith
Copy link
Collaborator

keith commented Mar 1, 2018

Heads up this is still an issue with Xcode 9.2's bundled Swift

@slavapestov
Copy link
Member

The conformance should be rejected. The default implementation is not sufficiently visible and thus cannot witness a public protocol requirement.

@slavapestov
Copy link
Member

@swift-ci create

@slavapestov
Copy link
Member

#15735

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself optimized only Flag: An issue whose reproduction requires optimized compilation
Projects
None yet
Development

No branches or pull requests

5 participants