Uploaded image for project: 'Swift'
  1. Swift
  2. SR-4827

Incorrect result resolving type for Obj-C block

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Resolved
    • Priority: Medium
    • Resolution: Done
    • Component/s: Compiler
    • 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)

      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.

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              benasher44 Ben A
              Reporter:
              benasher44 Ben A
              Votes:
              3 Vote for this issue
              Watchers:
              10 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: