ARC overreleases objects referenced in blocks created and dispatched in for-loop

Originator:openradar
Number:rdar://12972343 Date Originated:2013-01-08
Status:Duplicate/12969722 Resolved:
Product:Developer Tools (ARC + blocks) Product Version:Xcode 4.5.2
Classification: Reproducible:Always
 
Summary:
When using a block in a dispatch_async call inside a for loop the objects referenced inside the block are overreleased when the loop does more than one iteration and a RELEASE configuration is used in the build process.

Steps to Reproduce:
Use this sample code for a Mac app:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSObject *theObject = [[NSObject alloc] init];
    dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(myQueue, ^(){
        NSString *description = [theObject description];
        NSLog(@"Successfully referenced object %@ in initial block", description);
    });
    
    for (int i = 0; i < 2; i++)
    {
        dispatch_async(myQueue, ^(){
            NSString *description = [theObject description];
            NSLog(@"Successfully referenced object %@ in loop block %d", description, i);
        });
    }
}



Enable ARC and set the Build configuration to "Release" in the scheme. 
Enable Zombies in the Diagnostics tab of the scheme.
Build and run.

You should see the following output:
    2013-01-08 11:15:49.182 BlocksAndARC[19274:4313] Successfully referenced object <NSObject: 0x101a26ab0> in initial block
    2013-01-08 11:15:49.183 BlocksAndARC[19274:4313] Successfully referenced object <NSObject: 0x101a26ab0> in loop block 0
    2013-01-08 11:15:49.183 BlocksAndARC[19274:4313] *** -[NSObject description]: message sent to deallocated instance 0x101a26ab0

with the debugger stopping at the call to "description" in the block in the second iteration of the for-loop.


This only happens if
- ARC is enabled
- a "Release" build configuration is used
- the for loop does more than one iteration 
- there is a similar dispatch_call before the for-loop 


Expected Results:
The automatically added retain and release calls are correctly balanced for objects referenced inside the block.

Actual Results:
The objects used inside the block are overreleased.

Regression:
I could only test it with Xcode 4.5.2.


Workaround:
Add a local variable inside the loop and copy the referenced object to it and use the new variable in the block like so:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSObject *theObject = [[NSObject alloc] init];
    dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(myQueue, ^(){
        NSString *description = [theObject description];
        NSLog(@"Successfully referenced object %@ in initial block", description);
    });
    
    for (int i = 0; i < 2; i++)
    {
        NSObject *theSameObject = theObject;
        dispatch_async(myQueue, ^(){
            NSString *description = [theSameObject description];
            NSLog(@"Successfully referenced object %@ in loop block %d", description, i);
        });
    }
}


Even stranger: the new variable does not even have to be referenced inside the block, just assigning the referenced one to it is enough to prevent the crash:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSObject *theObject = [[NSObject alloc] init];
    dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(myQueue, ^(){
        NSString *description = [theObject description];
        NSLog(@"Successfully referenced object %@ in initial block", description);
    });
    
    for (int i = 0; i < 2; i++)
    {
        NSObject *theSameObject = theObject;
        dispatch_async(myQueue, ^(){
            NSString *description = [theObject description];
            NSLog(@"Successfully referenced object %@ in loop block %d", description, i);
        });
    }
}






Notes:
When using a concurrent queue with NSZombies enabled it crashes on a release call. But this is probably just due to slightly different timing.

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!