Skip to content

[libc++] [libc++abi] Regression: std::make_exception_ptr breaks catching ObjC objects on rethrow #135089

@rsesek

Description

@rsesek

This is a regression introduced in LLVM commit 51e91b6 (PR #65534). The following code used to work prior to the change:

#include <exception>
#import <Foundation/Foundation.h>

NSError* RecoverException(const std::exception_ptr& exc) {
  try {
    std::rethrow_exception(exc);
  } catch (NSError* error) {
    return error;
  } catch (...) {
  }
  return nil;
}

int main() {
  NSError* error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil];
  std::exception_ptr exc = std::make_exception_ptr(error);
  NSError* recov = RecoverException(exc);
  if (recov) {
    printf("[+] PASS: %s\n", recov.description.UTF8String);
  } else {
    printf("[-] FAIL: Expected NSError, got nil\n");
  }
}

This kind of code may be written when working with an Objective-C++ codebase that uses exceptions, and NSError*/NSException* types need to be handled alongside C++ errors. I believe this is supported because Objective-C exceptions are interoperable with C++ exceptions.

This broke with the aforementioned commit because std::make_exception_ptr no longer internally throws the exception object and uses std::current_exception to capture the value. Instead, it directly allocates and populates the __cxxabiv1::__cxa_exception using the C++ type information. However, when an ObjC exception is thrown natively, it uses a different representation for the exception's typeinfo.

This is apparent when looking at the assembly output from clang++ -S. In the sample program above, the Catch TypeInfos in GCC_except_table0 for RecoverException() is a _OBJC_EHTYPE_$_NSError. This corresponds to the type stored in a struct objc_exception produced in the ObjC runtime when an object is thrown via objc_exception_throw.

But in the assembly produced after that commit, the template instantiation for std::exception_ptr std::make_exception_ptr<NSError*>(NSError*) produces an exception with a reference to the __ZTIP7NSError typeinfo. I.e., after the commit, the exception stores the C++ typeinfo for the object, rather than the ObjC typeinfo. When the exception is rethrown, the catch arms fail to match because the typeinfos between the catch arm and the exception object do not match.

Putting a breakpoint on RecoverException and then inspecting the argument shows this too. After the commit:

(lldb) p *(((__cxxabiv1::__cxa_exception*)exc.__ptr_)-1)
(__cxxabiv1::__cxa_exception) {
  reserve = 0x0000000000000000
  referenceCount = 1
  exceptionType = 0x00000001000040a0
  exceptionDestructor = 0x0000000100000b8c (cocoa-exc`std::exception_ptr std::make_exception_ptr[abi:ne180000]<NSError*>(NSError*)::'lambda'(void*)::__invoke(void*) at exception_ptr.h:91)
  unexpectedHandler = 0x0000000100141570 (libc++abi.1.dylib`demangling_unexpected_handler() at cxa_default_handlers.cpp:90)
  terminateHandler = 0x000000010014138c (libc++abi.1.dylib`demangling_terminate_handler() at cxa_default_handlers.cpp:39)
  nextException = nil
  handlerCount = 0
  handlerSwitchValue = 0
  actionRecord = 0x0000000000000000
  languageSpecificData = 0x0000000000000000
  catchTemp = 0x0000000000000000
  adjustedPtr = 0x0000000000000000
  unwindHeader = {
    exception_class = 4849336966747728640
    exception_cleanup = 0x0000000100177288 (libc++abi.1.dylib`__cxxabiv1::exception_cleanup_func(_Unwind_Reason_Code, _Unwind_Exception*) at cxa_exception.cpp:134)
    private_1 = 0
    private_2 = 0
  }
}
(lldb) p/s (((__cxxabiv1::__cxa_exception*)exc.__ptr_)-1)->exceptionType->name()
(const char *) "P7NSError" "P7NSError"
(lldb) x/2a (((__cxxabiv1::__cxa_exception*)exc.__ptr_)-1)->exceptionType
0x1000040a0: 0x0000000100183bc0 libc++abi.1.0.dylib`vtable for __cxxabiv1::__pointer_type_info + 16
0x1000040a8: 0x8000000100000cf7 (0x0000000100000cf7) cocoa-exc`typeinfo name for NSError*

Before the commit:

(lldb) p *(((__cxxabiv1::__cxa_exception*)exc.__ptr_)-1)
(__cxxabiv1::__cxa_exception) {
  reserve = 0x0000000000000000
  referenceCount = 1
  exceptionType = 0x00006000008c4088
  exceptionDestructor = 0x000000019290b1ec (libobjc.A.dylib`_objc_exception_destructor(void*))
  unexpectedHandler = 0x0000000192c8dd30 (libc++abi.dylib`demangling_unexpected_handler())
  terminateHandler = 0x0000000192914cd8 (libobjc.A.dylib`_objc_terminate())
  nextException = nil
  handlerCount = 0
  handlerSwitchValue = 1
  actionRecord = 0x0000000100000bc1 "\U00000001"
  languageSpecificData = 0x0000000100000bb0 "\xff\x9b\U00000015\U00000001\f\U0000001c\U00000004(\U00000001 ("
  catchTemp = 0x0000000100000a90
  adjustedPtr = 0x00006000008c4080
  unwindHeader = {
    exception_class = 4849336966747728640
    exception_cleanup = 0x0000000192ca1af4 (libc++abi.dylib`__cxxabiv1::exception_cleanup_func(_Unwind_Reason_Code, _Unwind_Exception*))
    private_1 = 0
    private_2 = 6171911792
  }
}
(lldb) p/s (((__cxxabiv1::__cxa_exception*)exc.__ptr_)-1)->exceptionType->name()
(const char *) "NSError" "NSError"
(lldb) x/2a (((__cxxabiv1::__cxa_exception*)exc.__ptr_)-1)->exceptionType
0x6000008c4088: 0x0000000202c44938 libobjc.A.dylib`objc_ehtype_vtable + 16
0x6000008c4090: 0x0000000194de7dfa "NSError"

Also filed as FB17179536.

Metadata

Metadata

Assignees

No one assigned

    Labels

    libc++libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.libc++abilibc++abi C++ Runtime Library. Not libc++.objective-c

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions