Under normal conditions, when a block is declared to return a value, but no return
statement actually appears in the block, Clang fails to compile it with an error (of a missing return value).
However, this breaks when that block contains @try{} @catch(...){}
or @try{} @finally{}
.
Does anyone know why?
The way I found this was when using @weakify()
and @strongify()
in RACExtScope
in ReactiveCocoa, in one block I forgot to return a signal. But the compiler didn't warn me and crashed on runtime, which lead me to dig into it, preprocess the code and find that this causes it. Any explanation would be very much appreciated, I honestly don't know why this would happen, thanks!
I also created a gist, in case someone had a comment/suggestion: https://gist.github.com/czechboy0/11358741
int main(int argc, const char * argv[])
{
id (^iReturnStuff)() = ^id() {
@try{} @finally{}
//if you comment out line 4, Clang will not compile this.
//if you leave it like this, Clang will compile and run this, even though
//there's no value being returned.
//is there something special in @try{} that turns off compiler errors?
};
return 0;
}
Clang's block specification makes brief mention of control flow in a block. I've reproduced it here (emphasis mine)
Reading through a little further, you really get the sense that exceptions in Objective-C are downright weird. From the section on exceptions
From the above, one could reasonably deduce that the ObjC exceptions specification is so fragile or malleable that not even the compiler writers can guarantee stable code against it, therefore they just disabled all reasonable termination checks in once @try-@catch are encountered.
This can also be seen in the code generated by Clang with and without the try-catches. First, without
This is pretty simple x86 that pushes a new stack frame, moves 0 (nil) into the return register, then returns. Now, with the try-catch block:
Besides the more complicated function proem, notice the lack of a proper
ret
. The function still has two exit points,and
The first is a relatively new feature of the language where, when in the tailcall position, it can be used to omit
-autoreleases
in favor of drawing upon thread-local variables by examining the code that came before it. The second begins immediate termination of the process and jumps into the C++ exception handling mechanism. What this means is that the function does, in fact, have the requisite exit points to keep CLANG from complaining about missing return statements. Unfortunately, what it also means is that CLANG's forgoing of messing with the ObjC exception mechanism can potentially message garbage, as you've seen. This is one of the reasons EXTScope has switched to using the@autoreleasepool
directive to eat that sigil.