Skip to content

[SR-4827] Incorrect result resolving type for Obj-C block #47404

Closed
@swift-ci

Description

@swift-ci
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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.compilerThe Swift compiler itselfcrashBug: A crash, i.e., an abnormal termination of softwaregood first issueGood for newcomersrun-time crashBug → crash: Swift code crashed during execution

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions