New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-772] Accessing NSBlockOperation.executionBlocks crashes at run time #43384
Comments
Just to add a bit of clarity from some of the research on this; a nearly identical bug came across my plate as a bug on NSOperation but from what I was able to determine is that the memory stride of the array in swift for closures is 2 pointers but the stride of the actual storage is 1 pointer size. If we declared the block as dispatch_block_t it "fixes" the problem; but that comes at some cost of trying to justify that return type since it seems incorrect in that scope. |
@phausler, you may have seen this already but I also just sent an email to swift-dev about this. I believe the issue is that |
I've looked into fixing this a couple times, but I don't understand the clang importer well enough to make any real progress. @DougGregor, could you advise as to what can be done to avoid losing the |
So sorry I missed this... the issue appears to be that the handling of block types in ImportType.cpp's `adjustTypeForConcreteImport` needs to leave the `@convention(block)` when importing a type as a generic argument, i.e., splitting ImportTypeKind::BridgedValue into two different cases, where the new case is for generic arguments. The new one (ImportTypeKind::GenericArgument?) would allow value/reference type bridging but not block bridging. Probably not metatype bridging, either. |
Hi @DougGregor... Looking at this again after a month, I realize that However, once that's done, the actual bridging of the array fails at runtime with
Because apparently, // this returns true
({ print("hello") } as @convention(block) ()->()) is @convention(block) () -> ()
// this returns false
unsafeBitCast({ print("hello") } as @convention(block) ()->(), AnyObject.self) is @convention(block) () -> () |
Hmm. I don't think we have a way to recognize an Objective-C object that represents a block dynamically. Maybe @jckarter has an idea? |
Yeah, there's no API for checking whether an object is a block that I know about. |
The Clang code that [generates the block layout](https://github.com/apple/swift-clang/blob/stable/lib/CodeGen/CGBlocks.cpp) implies that the isa will be `_NSConcreteGlobalBlock`, `_NSConcreteStackBlock`, or `_NSConcreteMallocBlock`, so we could probably handle this in the dynamic casting machinery that way. |
That's not an API guarantee, though, and the blocks runtime may produce an object of some arbitrary internal class when a block gets |
Either way, I'm not sure how the array-bridging machinery would be special-cased for blocks (or, maybe Any ideas for some other way this can be fixed? |
Array bridging relies on bridging of the element types, so if we could figure out the object-to-block bridging story, array-of-objects to array-of-blocks should fall out. This really means talking to the libdispatch folks to determine whether there's some query or set of possible classes we could use and that they would be willing to commit to. Of course, even assuming we can recognize the "isa", are we truly going to check the parameter and result types based on the encoded metadata? How far do we go with this? |
Any Cocoa API that returns an NSArray or any other collection of blocks must by contract copy the blocks (Any that do not are probably bugs imho). Effectively it is (slightly simplified): - (void)addExecutionBlock:(void (^)(void))aBlock {
@swift-cihronized(self) {
void (^block)(void) = Block_copy(aBlock); // we must copy here because we don't know where this block came from.
[_executionBlocks addObject: block];
Block_release(block);
}
}
- (NSArray *)executionBlocks {
NSArray *blocks;
@swift-cihronized(self) {
blocks = [_executionBlocks copy];
}
return [blocks autorelease];
} |
"Any Cocoa API that returns an NSArray or any other collection of blocks must by contract copy the blocks" But only when the objects are known at compile time to be block objects. I don't know of anything in Cocoa that performs a runtime check for block objects. |
So the issue is an NSMutableArray that takes no generic and converting that into a swift array? let block1: (Void) -> Void = {_ in }
let block2: (Void) -> Void = {_ in }
let a = NSArray(array: [block1, block2])
// cannot invoke initializer for type 'NSArray' with an argument list of type '(array: [(Void) -> Void])'
let b = [block1, block2] as [AnyObject]
// value of type '(Void) -> Void' does not conform to expected element type 'AnyObject' That seems to be covered. The compiler should be emitting isa's that register to _NSConcreteStackBlock, _NSConcreteMallocBlock and _NSConcreteGlobalBlock in contexts that are callable by swift (_NSConcreteAutoBlock and _NSConcreteFinalizingBlock probably don't apply since GC is not in the mix) so perhaps the isa check can be against a derivative of those? If this cannot be fixed by the compiler I guess Foundation could work around the issue by declaring them as dispatch_block_t, but that would potentially have some poor assumptions about interaction with dispatch_block_perform and friends. |
Additional Detail from JIRA
md5: 02f57aca2a222d4f0f0e648b6323e450
Issue Description:
Running this code under Xcode 8:
crashes with this backtrace:
Also filed as rdar://24745102
The text was updated successfully, but these errors were encountered: