[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
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!
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.