Description
Previous ID | SR-4827 |
Radar | rdar://problem/23666040 |
Original Reporter | benasher44 (JIRA User) |
Type | Bug |
Status | Resolved |
Resolution | Done |
Environment
macOS 10.12.4
Swift 3.1
Xcode 8.3.2
Also:
macOS 10.12.5
Swift 3.2
Xcode 9.0 beta (9M136h)
Additional Detail from JIRA
Votes | 3 |
Component/s | Compiler |
Labels | Bug, ClangImporter, RunTimeCrash, StarterBug |
Assignee | benasher44 (JIRA) |
Priority | Medium |
md5: 5590c8a0ad8e21dbbb40a05d1c250d51
is duplicated by:
- SR-8330 Obj-C methods returning @objc-marked swift classes are not visible in swift code in frameworks
- SR-8410 Swift Classes with Custom Objective C Class Names Not Visible to Swift
- SR-8653 Renaming protocol using @objc(RenamedProtocol) makes method unavailable
- SR-11337 Problem with @objc and renaming
- SR-11339 Problems when naming classes/protocols using @objc
Issue Description:
Consider the following files:
SampleClass.h
#import <Foundation/Foundation.h>
@class SomeFactory;
@interface SampleClass: NSObject
+ (void)performWithSample:(SampleClass *)sample completion:(void(^)(SampleClass *, SomeFactory *))completionBlock;
@end
@interface OtherSampleClass: NSObject
@property (nonatomic, strong) SomeFactory *factory;
@end
SampleClass.m
#import "SampleClass.h"
#import "Test-Swift.h"
@implementation SampleClass
+ (void)performWithSample:(SampleClass *)sample completion:(void(^)(SampleClass *, SomeFactory *))completionBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
SomeFactory *f = [[SomeFactory alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
completionBlock(sample, f);
});
});
}
@end
@implementation OtherSampleClass
@end
test.swift
import Foundation
@objc(SomeFactory)
final class _ObjCSomeFactory: NSObject {
}
final class SomeFactory {
}
final class SomethingElse {
let os = OtherSampleClass()
func foo() {
let s = SampleClass()
SampleClass.perform(withSample: s) { (sample, factory) in
print("\(type(of: factory))")
self.os.factory = factory
print("\(type(of: factory))")
}
}
}
func main() {
let s = SomethingElse()
s.foo()
}
main()
// Ensure program doesn't end before async blocks finish
let g = DispatchGroup()
g.enter()
g.wait()
This can be run using the following commands:
swiftc -c test.swift -import-objc-header Test-Bridging-Header.h -F/System/Library/Frameworks/Foundation.framework -emit-objc-header-path Test-Swift.h
swiftc -c test.swift -import-objc-header Test-Bridging-Header.h -F/System/Library/Frameworks/Foundation.framework
clang SampleClass.m -o SampleClass.o -c
swiftc -o test SampleClass.o test.o
./test
The program runs and prints the following:
Optional<SomeFactory>
Optional<SomeFactory>
Segmentation fault: 11 ./test
The type being printed here is incorrect. This should be printing Optional<_ObjCSomeFactory>
. The bad type-checker result can be further exposed by annotating the block like so with more type information:
…
SampleClass.perform(withSample: s) { (sample: SampleClass?, factory: _ObjCSomeFactory?) -> Void in
…
Updating the code like this causes a compiler error:
error: cannot convert value of type '(SampleClass?, _ObjCSomeFactory?) -> Void' to expected argument type '((SampleClass?, SomeFactory?) -> Void)!'
By adding these type annotations, it's exposed that the block in SampleClass.h has been resolved to have a parameter of type SomeFactory?
, when it should be _ObjCSomeFactory?
.
As for the segfault (in the actual program that I derived this example from) Instruments showed that it was crashing because the closure executed by SampleClass
in SomethingElse.foo
ends up over-releasing factory
when the closure is cleaned up (in _dispatch_call_block_and_release
). This seems like it might be related to the bad type resolution?
We stared at this code for a long time trying to figure out where there could possible be a memory management bug here, and then we arrived at the fact that the types, as Swift understands them, in the closure that's implemented in Swift, but defined and called in Obj-C, are incorrect. Those types being understood as correct seems like an important prerequisite for Swift getting the memory management correct in this scenario.