为什么Clang在没有return语句的块中对@try {}感到困惑? [英] Why is Clang confused by @try{} in a block with no return statement?

查看:316
本文介绍了为什么Clang在没有return语句的块中对@try {}感到困惑?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在正常情况下,当一个块被声明返回一个值,但是 return 语句实际上出现在块中时,Clang无法编译它,



但是,当该块包含 @try {} @catch(...){} @try {} @finally {}



p>

我发现这是使用 @weakify() @strongify / code>在 RACExtScope 在ReactiveCocoa,在一个块我忘了返回一个信号。但编译器没有警告我,并在运行时崩溃,这导致我深入,预处理的代码,发现这导致它。任何解释将非常感谢,我真的不知道为什么会发生,谢谢!



我也创建了一个提示,以防万一有人有评论/建议: https://gist.github.com/czechboy0/11358741

  int main(int argc,const char * argv [])
{
id(^ iReturnStuff)()= ^ id b @try {} @finally {}
//如果你注释掉第4行,Clang不会编译这个。
//如果你这样离开,Clang会编译并运行这个,即使
//没有返回值。
//在@try {}中有什么特别的,关闭编译器错误?
};
return 0;
}


解决方案

Clang的块规范简要提到了块中的控制流。我在这里复制了(强调我的)


一个Block的复合语句很像一个函数体
尊重控制流中的goto,break和continue不要
逃离块。 异常被正常处理,因为当抛出
时,它们会弹出堆栈框架,直到找到catch子句。




再读一点,你真的得到的意义,Objective-C中的异常是非常奇怪的。从异常部分


标准的Cocoa约定是异常信号程序员
错误,并且不打算从中恢复。默认情况下,代码
异常安全会强加严格的运行时和代码大小
对通常不关心
异常安全性的代码的处罚。因此,默认情况下,ARC生成的代码泄露
异常,这是正常的,如果过程将是
立即终止。从例外中恢复
的程序应该启用该选项。


从上面可以合理地推断, ObjC异常规范是如此脆弱或可塑性,甚至编译器写者也不能保证稳定的代码,因此他们只是在遇到@ try- @ catch时禁用所有合理的终止检查。



这也可以在Clang生成的带有和不带try-catch的代码中看到。首先,没有

  ___ main_block_invoke:
pushq%rbp
movq%rsp,%rbp
movabsq $ 0,%rax
movq%rdi,-8(%rbp)
movq%rdi,-16(%rbp)
popq%rbp
ret

这是非常简单的x86推送一个新的栈帧,移动0(nil)到返回寄存器,然后返回。现在,使用try-catch块:

  ___ main_block_invoke:
pushq%rbp
movq%rsp, %rbp
subq $ 64,%rsp
movq%rdi,-16(%rbp)
movq%rdi,-24(%rbp)
movb $ 0, rbp)
movl -32(%rbp),%eax
testb $ 1,-25(%rbp)
movl%eax,-48(%rbp)## 4字节Spill
jb LBB1_1
jmp LBB1_3
LBB1_1:
callq _objc_exception_rethrow
jmp LBB1_2
LBB1_2:
LBB1_3:
movl -48 rbp),%eax ## 4-byte Reload
movl%eax,-32(%rbp)
movq -8(%rbp),%rdi
addq $ 64,%rsp
popq%rbp
jmp _objc_autoreleaseReturnValue ## TAILCALL
LBB1_4:
movl%edx,%ecx
movq%rax,-40(%rbp)
movl% ecx,-44(%rbp)
testb $ 1,-25(%rbp)
jne LBB1_5
jmp LBB1_7
LBB1_5:
callq _objc_end_catch
LBB1_6
LBB1_6:
jmp LBB1_7
LBB1_7:
jmp LBB1_8
LBB1_8:
movq -40(%rbp),%rdi
callq __Unwind_Resume
LBB1_9:
movq%rdx,-56(%rbp)## 8字节溢出
movq%rax,-64(%rbp)## 8字节溢出
callq _objc_terminate

除了更复杂的函数之外,注意缺少一个合适的 ret 。该函数仍然有两个退出点:

  jmp _objc_autoreleaseReturnValue 

  call _objc_terminate 

第一个是语言的一个相对较新的特性,当处于tailcall位置时,它可以用来省略 autoreleases 有利于通过检查前面的代码来绘制线程局部变量。第二个开始立即终止进程并跳转到C ++异常处理机制。这意味着,函数实际上具有必要的退出点,以防止CLANG抱怨缺少返回语句。不幸的是,这也意味着,CLANG的前述乱丢ObjC异常机制可能潜在消息垃圾,正如你所见。这是EXTScope 切换到使用 @autoreleasepool 指令来吃那个sigil。


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)

The compound statement of a Block is treated much like a function body with respect to control flow in that goto, break, and continue do not escape the Block. Exceptions are treated normally in that when thrown they pop stack frames until a catch clause is found.

Reading through a little further, you really get the sense that exceptions in Objective-C are downright weird. From the section on exceptions

The standard Cocoa convention is that exceptions signal programmer error and are not intended to be recovered from. Making code exceptions-safe by default would impose severe runtime and code size penalties on code that typically does not actually care about exceptions safety. Therefore, ARC-generated code leaks by default on exceptions, which is just fine if the process is going to be immediately terminated anyway. Programs which do care about recovering from exceptions should enable the option.

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

___main_block_invoke:
    pushq   %rbp
    movq    %rsp, %rbp
    movabsq $0, %rax
    movq    %rdi, -8(%rbp)
    movq    %rdi, -16(%rbp)
    popq    %rbp
    ret

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:

___main_block_invoke:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $64, %rsp
    movq    %rdi, -16(%rbp)
    movq    %rdi, -24(%rbp)
    movb    $0, -25(%rbp)
    movl    -32(%rbp), %eax
    testb   $1, -25(%rbp)
    movl    %eax, -48(%rbp)         ## 4-byte Spill
    jne LBB1_1
    jmp LBB1_3
    LBB1_1:
    callq   _objc_exception_rethrow
    jmp LBB1_2
    LBB1_2:
    LBB1_3:
    movl    -48(%rbp), %eax         ## 4-byte Reload
    movl    %eax, -32(%rbp)
    movq    -8(%rbp), %rdi
    addq    $64, %rsp
    popq    %rbp
    jmp _objc_autoreleaseReturnValue ## TAILCALL
    LBB1_4:
    movl    %edx, %ecx
    movq    %rax, -40(%rbp)
    movl    %ecx, -44(%rbp)
    testb   $1, -25(%rbp)
    jne LBB1_5
    jmp LBB1_7
    LBB1_5:
    callq   _objc_end_catch
    jmp LBB1_6
    LBB1_6:
    jmp LBB1_7
    LBB1_7:
    jmp LBB1_8
    LBB1_8:
    movq    -40(%rbp), %rdi
    callq   __Unwind_Resume
    LBB1_9:
    movq    %rdx, -56(%rbp)         ## 8-byte Spill
    movq    %rax, -64(%rbp)         ## 8-byte Spill
    callq   _objc_terminate

Besides the more complicated function proem, notice the lack of a proper ret. The function still has two exit points,

jmp   _objc_autoreleaseReturnValue

and

call  _objc_terminate

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.

这篇关于为什么Clang在没有return语句的块中对@try {}感到困惑?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆