不带 return 语句的 Obj-C 方法如果包含 @try @catch 块,则对其进行编译

Obj-C method without return statement compiles if it includes a @try @catch block

提问人:isaiah 提问时间:7/4/2020 更新时间:10/22/2021 访问量:326

问:

问:如何让 Xcode 报告丢失的退货?

下面是一个新的 Xcode 11 测试应用程序。我添加了一个空的 @try/@catch 块且没有返回值的方法。这样可以编译和分析,而不会出现警告或错误。shouldReturnAnObject

#import "AppDelegate.h"

@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@end

@implementation AppDelegate
- (nonnull id)shouldReturnAnObject {
    @try {
    } @catch (NSException *exception) {
    } @finally {
    }
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    id obj = [self shouldReturnAnObject];
    NSLog(@"obj: %@", obj);
}
@end

如果我从中删除@try块,则无法编译并显示错误:。这是我期望的任何没有返回返回的方法的行为。Control reaches end of non-void function

版本:Xcode 11.5, macOS 10.15.5.未更改生成设置。 添加不会更改行为。-Wall

我知道 Cocoa 的最佳实践是仅将异常用作例外。真正的应用很少使用异常,只是为了防止真正的异常行为。我提供了一个简单的测试应用程序来专注于这个问题。

我相信这种行为在 Xcode/Clang 中已经存在了很长时间。如果这种行为是故意的,有人可以解释为什么这种行为存在吗?

相关信息: 我还在以下语言中构建了快速测试应用程序:

  • C++ (Clang++):编译失败并出现错误-Wreturn-type
  • Java():编译失败,并显示error: missing return statement
  • Objective-C++(使用 .mm 文件):无警告、无错误、编译成功
objective-c 编译器错误 返回 try-catch 警告

评论

1赞 skaak 7/4/2020
奇怪 - 即使你把它包装成一些任意的东西,它仍然不会给出错误。即使你把它嵌套成两个,它仍然没有。try ... catchifif
1赞 jtbandes 7/5/2020
@skaak 这真的很奇怪,特别是因为它的工作方式,编译器通常认为它永远不会被执行。关于try/catch的某些事情导致它“放弃”流分析。if (false)
0赞 isaiah 10/28/2021
感谢 @jtbandes 在此处提交 Clang 项目: bugs.llvm.org/show_bug.cgi?id=46693 – 截至 21 年 10 月,错误中也有一些新的进展: reviews.llvm.org/D112287

答:

1赞 jtbandes 7/5/2020 #1

对我来说,这绝对是一个编译器错误。(您可以在 https://bugs.llvm.org/ 提交错误)证据如下。

我尝试了以下方法,但问题仍然存在:

  • @try {} @catch (NSException *e) {}没有@finally
  • @try {} @finally {}没有@catch
  • +而不是-
  • 地址清理器
  • 未定义的行为清理器
  • -Weverything

尽管编译器拒绝生成警告,但您可以看到使用 LLDB 生成了一个实际上完全空的方法体(启用了优化):

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x0000000100000eb4 asdf`-[MyObj shouldReturnAnObject](self=0x0000000103108440, _cmd="shouldReturnAnObject") at main.m:23:1 [opt]
    frame #1: 0x0000000100000eee asdf`main(argc=<unavailable>, argv=<unavailable>) at main.m:31:79 [opt]
    frame #2: 0x00007fff684f6cc9 libdyld.dylib`start + 1
    frame #3: 0x00007fff684f6cc9 libdyld.dylib`start + 1
(lldb) disassemble
asdf`-[MyObj shouldReturnAnObject]:
    0x100000eb0 <+0>: pushq  %rbp
    0x100000eb1 <+1>: movq   %rsp, %rbp
->  0x100000eb4 <+4>: popq   %rbp
    0x100000eb5 <+5>: retq   
    0x100000eb6 <+6>: nopw   %cs:(%rax,%rax)

https://godbolt.org/z/ZkF48u 显示了相同的内容,尽管程序无法链接,因为那里没有可用的 Obj-C 运行时。你可以尝试不同的编译器版本——事实上,这个问题似乎至少从 Clang 3.0.0 开始就已经存在了。

最后,通过一些运气/小心,您可以观察到缺少返回值会导致未定义的行为。在这里,程序只是抓取堆栈上已经存在的任何内容,例如字符串,而不是缺少的返回值:

评论

0赞 skaak 7/5/2020
绝对是一个错误。我尝试让编译器以各种方式抱怨/解决问题 - 将 if 或 for 放入其中,但没有任何效果。我希望在尝试和我认为错误起源的消息定义之间设置一些障碍。但是,如果我将该尝试放在函数内的某种块中,那么它就会开始抱怨返回值。try
0赞 skaak 7/5/2020
基于此 - 如果您将其隐藏在块中,我想带有 try catch 的消息的处理方式与没有 try catch 的“正常”消息不同,并且差异在于错误。
2赞 skaak 7/5/2020
无论如何 - 你用堆栈做什么很可怕,但也很酷。你刚刚把 Objective-C 变成了一个巨大的 RPN 计算器。想想这开辟了所有新的可能性。你可以用这种方式编写一些最难读但仍然有效的代码。
0赞 skaak 7/5/2020
对不起,所有的混乱 - 问题绝对是尝试的消息。获取任何更复杂的消息并删除 return 语句。这立即在 Xcode 中亮起,并出现“non-void message no return value”错误。然后在邮件的任意位置添加@try,它就会消失。
0赞 jtbandes 7/6/2020
我想如果我尽可能准确,它实际上不是堆栈,而是寄存器。eax
0赞 isaiah 7/6/2020 #2

后续说明:

在 clang 中找到案件的功劳归@jtbandes。 看起来这个案子只是没有实施。https://github.com/llvm/llvm-project/blob/master/clang/lib/Analysis/CFG.cpp#L3666

他还追踪了 11 年前的原始“FIXME”提交。https://twitter.com/jtbandes/status/1279870326784929792

我已经向 Apple 提交了 ~radar~ Feedback Assistant bug:FB7851551

我没有 llvm 贡献者帐户,并且由于垃圾邮件,他们没有接受新成员,所以不能在那里提交错误。如果其他人愿意,那就太棒了。如果他们确实授予我一个帐户,我也会在那里跟进错误报告。

这样一来,我认为这个案子就结了。也许运气好的话,它可以在今年秋天进入 SDK 的最终版本。😃 感谢大家帮助我找到这个。

1赞 thakis 10/22/2021 #3

这是一个错误。此字段存在一个 LLVM 错误,https://llvm.org/PR46693

评论

2赞 jtbandes 10/22/2021
我们已经走了整整一圈!由于这个问题,我去年提交了这个错误;请参阅上面关于我答案的评论线程。😄 感谢您的修复工作!
0赞 isaiah 10/28/2021
@jtbandes - 感谢您在错误中提及。:-)