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

Viewed 257

Question: How do I get Xcode to report the missing return?

Below is a new Xcode 11 test-app. I added the shouldReturnAnObject method with an empty @try/@catch block and no return value. This compiles and analyzes without warnings or errors.

#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

If I remove the @try block from it fails to compile with the error: Control reaches end of non-void function. This is the behavior I expect for any method with no return return.

Versions:Xcode 11.5, macOS 10.15.5. No build settings changed. Adding -Wall doesn't change the behavior.

I understand Cocoa best practice is to use exceptions only as exceptional. The real app uses exception rarely, and only to guard against truly exceptional behavior. I'm providing a simple test-app to focus on the issue.

I believe this behavior has existed in Xcode/Clang for a long time. If the behavior is intentional can someone explain why this behavior exists?whi

Related info: I have also built quick test apps in:

  • C++ (Clang++) : compile fails with the error -Wreturn-type
  • Java () : compile fails with error: missing return statement
  • Objective-C++ (using a .mm file): no warnings, no errors, compile succeeds
3 Answers

To me, this definitely appears to be a compiler bug. (You can file a bug at https://bugs.llvm.org/) Evidence follows.

I tried the following things and the problem persists:

  • @try {} @catch (NSException *e) {} with no @finally
  • @try {} @finally {} with no @catch
  • + instead of -
  • Address Sanitizer
  • Undefined Behavior Sanitizer
  • -Weverything

Despite the compiler's refusal to generate a warning, you can see using LLDB that it generates an effectively completely empty method body (with optimization enabled):

(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 shows the same, although the program fails to link because there's no Obj-C runtime available there. And you can try different compiler versions — indeed, the problem seems to have existed at least since Clang 3.0.0.

Finally, with some luck/care you can observe that the lack of return value results in undefined behavior. Here, in lieu of the missing return value, the program just grabs whatever happened to be on the stack already, such as a string:

Follow-up notes:

Credit for finding the case in clang goes to @jtbandes. It looks like this case is just unimplemented. https://github.com/llvm/llvm-project/blob/master/clang/lib/Analysis/CFG.cpp#L3666

He also tracked down the original "FIXME" commit 11 years ago. https://twitter.com/jtbandes/status/1279870326784929792

I've submitted ~radar~ Feedback Assistant bug to Apple:FB7851551

I don't have an llvm contributor account and they're not taking new members due to spam, so can't submit a bug there. If someone else wants to, that would be awesome. And if they do grant me an account I'll follow up with the bug report there too.

And with that, I think it's case closed. Maybe with a little luck this can make it into final releases of SDKs this fall. Thanks everyone for helping me find this one.

Related