[clang] Incorrect ARC codegen for block copies when optimizations enabled

Originator:michael.ash
Number:rdar://13249661 Date Originated:19-Feb-2013 08:20 PM
Status:Open Resolved:
Product:Developer Tools Product Version:4.6
Classification:Crash/hang/data loss Reproducible:Always
 
Summary:

When optimizations are enabled, clang with ARC incorrectly generates code for code of the form:

    void (^callBlock)(id) = (id)block;

Specifically, it generates a objc_retainBlock/objc_release pair with an incorrect argument to objc_release, potentially causing a memory leak.


Steps to Reproduce:

Compile and run the following code with ARC and -O3:

#include <Foundation/Foundation.h>



void f(void (^block)(id)) {
    void (^callBlock)(id) = (id)block;
    callBlock(nil);
}

int main(int argc, char **argv)
{
    for(int i = 0; i < 1000000; i++)
        f(^(id x){ [x initWithFormat: @"%d", i]; });
    
    NSLog(@"done");
    while(1)
        sleep(1);
    
    return 0;
}

When it prints "done", run a leak checker on the process.


Expected Results:

No leaks should be reported.


Actual Results:

One block is leaked for each iteration of the loop.


Regression:

The bug appears to be new as of Xcode 4.6 or 4.5.2.


Notes:

Here is the generated assembly code for the function:

	movq	%rdi, %rbx
	callq	_objc_retainBlock
	movq	%rax, %rdi
	xorl	%esi, %esi
	callq	*16(%rax)
	movq	%rbx, %rdi
	addq	$8, %rsp
	popq	%rbx
	popq	%rbp
	jmp	_objc_release           ## TAILCALL

As I understand it, the calls are equivalent to:

    callBlock = objc_retainBlock(block);
    callBlock();
    objc_release(block);

The obj_release should be called on callBlock, but is instead being called on block. In the case where the block is already on the heap, this mistake is harmless, as block and callBlock will contain the same pointer. However, in case where the block is originally on the stack, this is disastrous. In that case, block still points to the stack block, while callBlock points to the heap copy. Releasing the stack block does nothing, leaving the heap block behind to leak.

Because blocks, especially ephemeral ones, can capture large amounts of data, the potential leak here is huge.

Comments

Replicated with Xcode 4.5.2 (clang/llvm 4.1) running under 10.7.5 at all of [-O,O1], -O2, -O3, and -Os optimization levels.

Just to add a some data points I replicated this with Xcode 4.5.2 (clang 4.1) running under 10.7.5 at all of [-O,O1], -O2, -O3, and -Os optimization levels. Also verified that it doesn't seem to happen with no optimization turned on (-O0).

Only change is addition of (void) to block contents initWithFormat: call to silence compiler warning.


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!