Why do nil / NULL blocks cause bus errors when run?

Objective CObjective C-Blocks

Objective C Problem Overview


I started using blocks a lot and soon noticed that nil blocks cause bus errors:

typedef void (^SimpleBlock)(void);
SimpleBlock aBlock = nil;
aBlock(); // bus error

This seems to go against the usual behaviour of Objective-C that ignores messages to nil objects:

NSArray *foo = nil;
NSLog(@"%i", [foo count]); // runs fine

Therefore I have to resort to the usual nil check before I use a block:

if (aBlock != nil)
    aBlock();

Or use dummy blocks:

aBlock = ^{};
aBlock(); // runs fine

Is there another option? Is there a reason why nil blocks couldn’t be simply a nop?

Objective C Solutions


Solution 1 - Objective C

I'd like to explain this a bit more, with a more complete answer. First let's consider this code:

#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {    
    void (^block)() = nil;
    block();
}

If you run this then you'll see a crash on the block() line that looks something like this (when run on a 32-bit architecture - that's important):

> EXC_BAD_ACCESS (code=2, address=0xc)

So, why is that? Well, the 0xc is the most important bit. The crash means that the processor has tried to read the information at memory address 0xc. This is almost definitely an entirely incorrect thing to do. It's unlikely there's anything there. But why did it try to read this memory location? Well, it's due to the way in which a block is actually constructed under the hood.

When a block is defined, the compiler actually creates a structure on the stack, of this form:

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

The block is then a pointer to this structure. The fourth member, invoke, of this structure is the interesting one. It is a function pointer, pointing to the code where the block's implementation is held. So the processor tries to jump to that code when a block is invoked. Notice that if you count the number of bytes in the structure before the invoke member, you'll find that there are 12 in decimal, or C in hexadecimal.

So when a block is invoked, the processor takes the address of the block, adds 12 and tries to load the value held at that memory address. It then tries to jump to that address. But if the block is nil then it'll try to read the address 0xc. This is a duff address, clearly, and so we get the segmentation fault.

Now the reason it must be a crash like this rather than silently failing like an Objective-C message call does is really a design choice. Since the compiler is doing the work of deciding how to invoke the block, it would have to inject nil checking code everywhere a block is invoked. This would increase code size and lead to bad performance. Another option would be to use a trampoline which does the nil checking. However this would also incur performance penalty. Objective-C messages already go through a trampoline since they need to look up the method that will actually be invoked. The runtime allows for lazy injection of methods and changing of method implementations, so it's already going through a trampoline anyway. The extra penalty of doing the nil checking is not significant in this case.

I hope that helps a little bit to explain the rationale.

For more information, see my blog posts.

Solution 2 - Objective C

Matt Galloway's answer is perfect! Great read!

I just want to add that there are some ways to make life easier. You could define a macro like this:

#define BLOCK_SAFE_RUN(block, ...) block ? block(__VA_ARGS__) : nil

It can take 0 – n arguments. Example of usage

typedef void (^SimpleBlock)(void);
SimpleBlock simpleNilBlock = nil;
SimpleBlock simpleLogBlock = ^{ NSLog(@"working"); };
BLOCK_SAFE_RUN(simpleNilBlock);
BLOCK_SAFE_RUN(simpleLogBlock);

typedef void (^BlockWithArguments)(BOOL arg1, NSString *arg2);
BlockWithArguments argumentsNilBlock = nil;
BlockWithArguments argumentsLogBlock = ^(BOOL arg1, NSString *arg2) { NSLog(@"%@", arg2); };
BLOCK_SAFE_RUN(argumentsNilBlock, YES, @"ok");
BLOCK_SAFE_RUN(argumentsLogBlock, YES, @"ok");

If you want to get the return value of the block and you are not sure if the block exists or not then you are probably better off just typing:

block ? block() : nil;

This way you can easily define the fallback value. In my example 'nil'.

Solution 3 - Objective C

Caveat: I'm no expert in Blocks.

Blocks are objective-c objects but calling a block is not a message, although you could still try [block retain]ing a nil block or other messages.

Hopefully, that (and the links) helps.

Solution 4 - Objective C

This is my simple nicest solution… Maybe there is possible to write one universal run function with those c var-args but I don’t know how to write that.

void run(void (^block)()) {
    if (block)block();
}

void runWith(void (^block)(id), id value) {
    if (block)block(value);
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionzoulView Question on Stackoverflow
Solution 1 - Objective CmattjgallowayView Answer on Stackoverflow
Solution 2 - Objective ChfossliView Answer on Stackoverflow
Solution 3 - Objective CStephen FurlaniView Answer on Stackoverflow
Solution 4 - Objective CRenetikView Answer on Stackoverflow