Swift Mangling Regret: Private Discriminators

Part of the Swift Regrets series.

One of the fundamental things about private is that other files’ source shouldn’t be affected by a private declaration, at least not directly.1 That includes at the symbol / linking layer. C accomplishes this by not exposing symbols for private decls at all (except in debug info, and LTO maybe somehow?). Java always nests private decls inside public/package ones (and doesn’t have extensions), so they can’t ever conflict across files. But Swift allows top-level private decls, and has whole-module (cross-file) optimization. And we use our mangling for indexing too. So really two private decls in different files of the same module should get different mangled names. Additionally, these names should be deterministic, so that binaries are reproducible, tests are deterministic, indexes don’t constantly need updates, and patch-updates to existing apps can be smaller. So no timestamps or random numbers and probably no full paths either.

I was the one implementing this in pre-Swift-1, and so I just went with something simple to start: a hex-encoded hash of the module name and file basename. The module name isn’t strictly necessary but it’s minor obfuscation against someone building a reverse mapping. I picked a hash implementation LLVM had lying around, MD5. This is not cryptographically secure and wasn’t meant to be, but PSA: your file names are leaked just a little into your Swift binaries. The hash gets hex-encoded and included as necessary into mangled names.

(I knew I wanted to be able to change this in the future, so the “private discriminator” is length-prefixed in a mangled name, like an identifier. Which broke when the hex-encoded hash had a leading decimal digit. Oops. I prepended an underscore and moved on.)

And then disaster struck. Remember the archived classes from last time? The ones whose Objective-C runtime names couldn’t change, because you’d lose user data?

Some of them were private.

So now the old mangling is stuck with a long, not-particularly-good hash as its private discriminator, and there’s not enough of a reason for the new mangling to diverge from that while the old mangling’s still around…and now you know why Swift doesn’t let you have two files with the same name in the same module, even if they’re in different directories. Because the private discriminator only includes the basename in the hash, and changing that would break compatibility.

Compatibility is hard, y’all. Try not to commit to it before you’re ready.

  1. I’m using “private” to mean “file-private”, including both private and fileprivate in Swift. This is partly for simplicity and partly because I still think they shouldn’t have been separated. See SE-0025 and SE-0169↩︎