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

Protocol conformance is “stripped” from static library. #62056

Closed
mbrandonw opened this issue Nov 11, 2022 · 10 comments
Closed

Protocol conformance is “stripped” from static library. #62056

mbrandonw opened this issue Nov 11, 2022 · 10 comments
Labels
casting Feature: explicit casting (is, as, as? and as!) conformances Feature → protocol: protocol conformances feature A feature request or implementation off topic Resolution: Is beyond the scope of the Swift project (Xcode, proprietary Apple frameworks, etc.) static libraries Xcode Flag: Involves Xcode

Comments

@mbrandonw
Copy link
Contributor

mbrandonw commented Nov 11, 2022

Describe the bug

NB: I'm not sure if this is a Swift bug or an Xcode bug, so I have also filed feedback FB11779019.

It is possible to have a protocol conformance in a static library that is not recognizable from another library. If you do a dynamic type check via is any Q it will fail even though the value definitely conforms to Q.

Suppose you have the following protocol hierarchy in a static library:

protocol P {}
protocol Q: P {}

And you create a conformance to P and Q in separate files:

// ConformanceP.swift
struct Conformance: P {}

// ConformanceQ.swift
extension Conformance: Q {}

Then, in a separate library, checking for a dynamic conformance fails, but a direct check succeeds:

func isAnyQ(_ p: any P) -> Bool {
  p is any Q
}
isAnyQ(Conformance())  // false
Conformance() is any Q // true

I have a project prepared to demonstrate the problems. Open the StaticStrippingApp.swift file for instructions on how to reproduce.

So far I have found 3 ways to prevent Swift from stripping the Q conformance:

  • Put the conformance of P and Q in the same file.
  • Add a symbol to the file with the Q conformance and refer to that symbol from somewhere else. This seems to prevent Swift from stripping the Q conformance.
  • Use a dynamic library. If you move the conformance and protocol files from StaticLibrary to DynamicLibrary you will see that the problem is fixed.

Expected behavior

I would expect that dynamically checking a conformance on Q to return true instead of false.

Environment (please fill out the following information)

  • OS: macOS 13.0 (22A380)
  • Xcode Version/Tag/Branch: Version 14.1 (14B47b)
@mbrandonw mbrandonw added the bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. label Nov 11, 2022
@tbkka
Copy link
Contributor

tbkka commented Nov 11, 2022

CC: @mikeash @al45tair @jckarter

@mikeash
Copy link
Contributor

mikeash commented Nov 11, 2022

Static linking normally requires something else to refer to a symbol for it to be incorporated into the final product. Nothing refers to protocol conformance descriptors, so they won't be included.

Apple's linker provides a -ObjC flag that relaxes this requirement for certain ObjC data structures (this same problem frequently shows up with ObjC categories). I believe it also applies to Swift structures these days, so give that a try by adding -ObjC to your project's linker flags. (Note: this goes in the flags for the final product, not for the static library.)

Failing that, the linker also has a big hammer in the form of -all_load, which relaxes this requirement for everything in the static library. That should make this work if -ObjC does not.

@mbrandonw
Copy link
Contributor Author

@mikeash Thanks for the information. That is very helpful to know, and -ObjC does fix it.

Is this considered the expected behavior of static libraries? Seems like a very serious gotcha.

@mikeash
Copy link
Contributor

mikeash commented Nov 11, 2022

It is expected, and rather unfortunate. The overall problem is fundamental to the model of how static linking works. It would probably make sense to make -ObjC the default when linking ObjC and Swift, at least. I'll raise that with the appropriate folks.

@mbrandonw
Copy link
Contributor Author

Ok, thanks again for the info! It was very helpful. I'll close this out.

@mikeash
Copy link
Contributor

mikeash commented Nov 11, 2022

Glad to help, and sorry for the trouble!

@AnthonyLatsis AnthonyLatsis added not a bug Resolution → not a bug: Reported as a bug but turned out to be expected behavior or programmer error conformances Feature → protocol: protocol conformances static libraries casting Feature: explicit casting (is, as, as? and as!) bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. and removed bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. not a bug Resolution → not a bug: Reported as a bug but turned out to be expected behavior or programmer error labels Nov 11, 2022
@AnthonyLatsis
Copy link
Collaborator

@mbrandonw Should we keep this open, since the behavior is not desirable, although expected? We definitely want something done about it sooner or later.

@mbrandonw mbrandonw reopened this Nov 11, 2022
@mbrandonw
Copy link
Contributor Author

Sure thing, opened!

@al45tair al45tair added not a bug Resolution → not a bug: Reported as a bug but turned out to be expected behavior or programmer error and removed bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. labels Nov 14, 2022
@al45tair
Copy link
Contributor

It definitely isn't a Swift bug — at most, it's a request for a linker feature (making -ObjC the default if the linker detects Swift or ObjC code), or possible an Xcode feature (automatically adding -ObjC when linking static libraries that contain Swift or ObjC code), and for those you should file a Feedback request (if you do that, please post the FB number here).

Static linking (with a library) has always worked by searching the library for symbols that are undefined and including just the object files (within the library) that define those symbols, then repeating until all symbols have been located. The point of doing this is that, for statically linked executables, you don't want to pull in e.g. the entire C library just because they might at some point have called strxfrm(); if you did that, every executable would be huge.

We don't necessarily know at compile time which protocol conformances we're going to need, so we can't emit references to them to cause the static linker to bring in the right object file(s) automatically, hence the need to tell the linker to enable the unusual behaviour that is also needed for ObjC code where it will include object files simply because they have ObjC or Swift metadata in them, even if there are no references to that metadata from outside.

I'm going to close it again here, but as I say, please do file a feedback request for the linker folks.

@mbrandonw
Copy link
Contributor Author

Hi @al45tair, done: FB11788325 (gist).

@AnthonyLatsis AnthonyLatsis added Xcode Flag: Involves Xcode off topic Resolution: Is beyond the scope of the Swift project (Xcode, proprietary Apple frameworks, etc.) feature A feature request or implementation and removed not a bug Resolution → not a bug: Reported as a bug but turned out to be expected behavior or programmer error labels Nov 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
casting Feature: explicit casting (is, as, as? and as!) conformances Feature → protocol: protocol conformances feature A feature request or implementation off topic Resolution: Is beyond the scope of the Swift project (Xcode, proprietary Apple frameworks, etc.) static libraries Xcode Flag: Involves Xcode
Projects
None yet
Development

No branches or pull requests

5 participants