Skip to content
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

Closed
jtbandes opened this issue Feb 19, 2016 · 15 comments
Closed

[SR-772] Accessing NSBlockOperation.executionBlocks crashes at run time #43384

jtbandes opened this issue Feb 19, 2016 · 15 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. crash Bug: A crash, i.e., an abnormal termination of software run-time crash Bug → crash: Swift code crashed during execution

Comments

@jtbandes
Copy link
Collaborator

Previous ID SR-772
Radar rdar://24745102
Original Reporter @jtbandes
Type Bug
Status Closed
Resolution Cannot Reproduce
Additional Detail from JIRA
Votes 0
Component/s
Labels Bug, RunTimeCrash
Assignee None
Priority Medium

md5: 02f57aca2a222d4f0f0e648b6323e450

Issue Description:

Running this code under Xcode 8:

import Foundation
let blop = BlockOperation()
blop.executionBlocks

crashes with this backtrace:

0  swift                    0x0000000106ddaa3d PrintStackTraceSignalHandler(void*) + 45
1  swift                    0x0000000106dda466 SignalHandler(int) + 470
2  libsystem_platform.dylib 0x00007fffc61f2bba _sigtramp + 26
3  libsystem_platform.dylib 0x00007f98f7500990 _sigtramp + 825286128
4  swift                    0x0000000104ed77e6 (anonymous namespace)::X86_64ABIInfo::computeInfo(clang::CodeGen::CGFunctionInfo&) const + 38
5  swift                    0x0000000104c70e37 clang::CodeGen::CodeGenTypes::arrangeLLVMFunctionInfo(clang::CanQual<clang::Type>, bool, bool, llvm::ArrayRef<clang::CanQual<clang::Type> >, clang::FunctionType::ExtInfo, llvm::ArrayRef<clang::FunctionProtoType::ExtParameterInfo>, clang::CodeGen::RequiredArgs) + 2839
6  swift                    0x00000001041bd29c swift::irgen::Signature::get(swift::irgen::IRGenModule&, swift::CanTypeWrapper<swift::SILFunctionType>) + 2524
7  swift                    0x000000010427c66e swift::irgen::prepareObjCMethodRootCall(swift::irgen::IRGenFunction&, swift::SILDeclRef, swift::CanTypeWrapper<swift::SILFunctionType>, swift::CanTypeWrapper<swift::SILFunctionType>, llvm::ArrayRef<swift::Substitution>, swift::irgen::ObjCMessageKind) + 110
8  swift                    0x00000001042ec86e (anonymous namespace)::IRGenSILFunction::visitFullApplySite(swift::FullApplySite) + 958
9  swift                    0x00000001042d7b28 swift::irgen::IRGenModule::emitSILFunction(swift::SILFunction*) + 9336
10 swift                    0x00000001041f9231 swift::irgen::IRGenerator::emitGlobalTopLevel() + 1329
11 swift                    0x00000001042bd34b performIRGeneration(swift::IRGenOptions&, swift::ModuleDecl*, swift::SILModule*, llvm::StringRef, llvm::LLVMContext&, swift::SourceFile*, unsigned int) + 1259
12 swift                    0x0000000104188c19 performCompile(swift::CompilerInstance&, swift::CompilerInvocation&, llvm::ArrayRef<char const*>, int&, swift::FrontendObserver*) + 23705
13 swift                    0x0000000104180f70 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 17856
14 swift                    0x000000010413d93e main + 8302
15 libdyld.dylib            0x00007fffc5fe5255 start + 1
Stack dump:
0.  Program arguments: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -frontend -c -primary-file - -target x86_64-apple-macosx10.9 -enable-objc-interop -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk -color-diagnostics -module-name main -o /var/folders/lp/hlnmlhtn79s4qvxx8c7bppbw0000gn/T/--81d07d.o 
1.  While emitting IR SIL function @main<unknown>:0: error: unable to execute command: Segmentation fault: 11

Also filed as rdar://24745102

@phausler
Copy link
Member

phausler commented Mar 3, 2016

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.

@jtbandes
Copy link
Collaborator Author

jtbandes commented Mar 3, 2016

@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 NSArray<void(^)(void)>* is imported as [()->()] rather than [@convention(block) ()->()].

@jtbandes
Copy link
Collaborator Author

jtbandes commented Mar 4, 2016

@jtbandes
Copy link
Collaborator Author

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 @convention(block) information when importing generic arguments?

@DougGregor
Copy link
Member

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.

@jtbandes
Copy link
Collaborator Author

Hi @DougGregor... Looking at this again after a month, I realize that ImportTypeKind::BridgedValue is only used for generic arguments. So it can simply be renamed to GenericArgument, and modified so that canBridgeTypes returns false.

However, once that's done, the actual bridging of the array fails at runtime with

"NSArray element failed to match the Swift Array Element type"

Because apparently, [an instance of NSMallocBlock] is @convention(block) ()->() returns false. Why might that be the case? Should that be fixed, or should we somehow attempt to avoid the sanity check here?

// 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) () -> ()

@DougGregor
Copy link
Member

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?

@jckarter
Copy link
Member

jckarter commented Jun 6, 2016

Yeah, there's no API for checking whether an object is a block that I know about.

@DougGregor
Copy link
Member

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.

@jckarter
Copy link
Member

jckarter commented Jun 6, 2016

That's not an API guarantee, though, and the blocks runtime may produce an object of some arbitrary internal class when a block gets _Block_copy-ed.

@jtbandes
Copy link
Collaborator Author

jtbandes commented Jun 6, 2016

Either way, I'm not sure how the array-bridging machinery would be special-cased for blocks (or, maybe [()->()] could be fixed, but wouldn't [[()->()]] need another special case, etc.?)

Any ideas for some other way this can be fixed?

@DougGregor
Copy link
Member

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?

@phausler
Copy link
Member

phausler commented Jun 6, 2016

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).
In this specific case BlockOperation internally copies the blocks when storing them into it's ivars, and then when returning the items it creates a new array to return to the client because of synchronization requirements.

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];
}

@gparker42
Copy link
Mannequin

gparker42 mannequin commented Jun 6, 2016

"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.

@phausler
Copy link
Member

phausler commented Jun 7, 2016

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.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@AnthonyLatsis AnthonyLatsis added the crash Bug: A crash, i.e., an abnormal termination of software label Dec 12, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. crash Bug: A crash, i.e., an abnormal termination of software run-time crash Bug → crash: Swift code crashed during execution
Projects
None yet
Development

No branches or pull requests

5 participants