swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.53k stars 10.35k forks source link

[SR-795] swiftc doesn't work with ScriptingBridge #43407

Open 05262b81-54a9-4fe1-bf6a-96f8042de10e opened 8 years ago

05262b81-54a9-4fe1-bf6a-96f8042de10e commented 8 years ago
Previous ID SR-795
Radar rdar://problem/17437506
Original Reporter @lilyball
Type Bug

Attachment: Download

Environment Xcode 7.3 beta 3 (7D141l) Apple Swift version 2.2 (swiftlang-703.0.6.5 clang-703.0.21) Target: x86_64-apple-macosx10.9
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 3 | |Component/s | Compiler | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: f286d808225a261022b3da23f5218625

Issue Description:

The way ScriptingBridge works is classes are created dynamically at runtime based on an application's scripting dictionary. In order to actually work with these classes, you use the sdp command-line tool to emit an Obj-C header from an app's scripting dictionary (which can be extracted with the sdef tool). This Obj-C header declares all the classes, but of course there are no @implementation blocks because the classes are created dynamically at runtime.

This works perfectly fine in Obj-C. Using these classes from Obj-C doesn't ever actually reference the metadata symbols, e.g. _OBJC_CLASS_$_iTunesApplication, because you never message a class directly or access any ivars. Instead you call something like +[SBApplication applicationWithBundleIdentifier:] and cast it to the expected dynamic type.

Unfortunately, in Swift, attempting to use this always emits references to symbols like _OBJC_CLASS_$_iTunesApplication, which fails at link-time. I'm not actually sure why it even does this. Given a project that generates the appropriate headers with sdp for iTunes, the following line is sufficient to reproduce the issue:

let app: iTunesApplication = unsafeDowncast(SBApplication(bundleIdentifier: "com.apple.iTunes")!)

Note the use of unsafeDowncast to eliminate the type-check. I don't know why Swift is emitting a reference to _OBJC_CLASS_$_iTunesApplication here, but it is, and so this fails at link-time.

I'm not entirely sure what the right solution here is. One possible solution is to add a Clang attribute that marks an @interface block to say that the class will exist at runtime and therefore no references to symbols like _OBJC_CLASS_$_iTunesApplication should be emitted, and then to teach swiftc to respect this (e.g. to make it dynamically look up the class at runtime instead of using the symbol). But beyond needing to change Clang, this also means needing to change sdp to emit the appropriate attribute.

Another solution might be to simply figure out why swiftc is emitting a reference to _OBJC_CLASS_$_iTunesApplication at all, and change swiftc to avoid emitting references to symbols like that for Obj-C classes imported via the bridging header unless there's no choice. But I assume that swiftc is emitting that reference for some reason, even though I don't know what it is, and presumably getting rid of this reference would have negative repercussions elsewhere. In addition, this also relies on everyone remembering to use unsafeDowncast() everywhere that involves a cast to one of these ScriptingBridge-generated classes.

A third option might involve teaching swiftc to treat subclasses of SBObject specially, though I don't know if that's sufficient (e.g. I don't know if references to protocols also involve any kind of symbol reference that would also cause a link error).

I've attached a sample project that demonstrates this issue.

05262b81-54a9-4fe1-bf6a-96f8042de10e commented 8 years ago

Upon revisiting this, I just realized where the _OBJC_CLASS_$_iTunesApplication references came from. unsafeDowncast only skips the checked cast under optimization. In debug mode, it emits the check, which causes the symbol. So this problem can be worked around by using unsafeBitCast instead of unsafeDowncast. But that's still awkward, and it's also still trivial to create other references to the _OBJC_CLASS_$_iTunesApplication symbol. For example, just passing the object to print() is sufficient to cause the error (presumably passing it to any generic function is problematic).