Skip to content

release/20.x: [libc++] Fix std::make_exception_ptr interaction with ObjC (#135386) #147554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: release/20.x
Choose a base branch
from

Conversation

llvmbot
Copy link
Member

@llvmbot llvmbot commented Jul 8, 2025

Backport fcc09b6

Requested by: @ldionne

Clang treats throwing/catching ObjC types differently from C++ types,
and omitting the `throw` in `std::make_exception_ptr` breaks ObjC
invariants about how types are thrown/caught.

Fixes llvm#135089

Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
(cherry picked from commit fcc09b6)
@llvmbot llvmbot requested a review from a team as a code owner July 8, 2025 15:42
@llvmbot llvmbot added this to the LLVM 20.X Release milestone Jul 8, 2025
@github-project-automation github-project-automation bot moved this to Needs Triage in LLVM Release Status Jul 8, 2025
@llvmbot
Copy link
Member Author

llvmbot commented Jul 8, 2025

@jyknight What do you think about merging this PR to the release branch?

@llvmbot llvmbot requested a review from jyknight July 8, 2025 15:42
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jul 8, 2025
@llvmbot
Copy link
Member Author

llvmbot commented Jul 8, 2025

@llvm/pr-subscribers-libcxx

Author: None (llvmbot)

Changes

Backport fcc09b6

Requested by: @ldionne


Full diff: https://github.com/llvm/llvm-project/pull/147554.diff

2 Files Affected:

  • (modified) libcxx/include/__exception/exception_ptr.h (+44-18)
  • (added) libcxx/test/std/language.support/support.exception/propagation/make_exception_ptr.objc.pass.mm (+44)
diff --git a/libcxx/include/__exception/exception_ptr.h b/libcxx/include/__exception/exception_ptr.h
index 6257e6f729bf3..b007e39db6fcb 100644
--- a/libcxx/include/__exception/exception_ptr.h
+++ b/libcxx/include/__exception/exception_ptr.h
@@ -15,6 +15,7 @@
 #include <__memory/addressof.h>
 #include <__memory/construct_at.h>
 #include <__type_traits/decay.h>
+#include <__type_traits/is_pointer.h>
 #include <cstdlib>
 #include <typeinfo>
 
@@ -62,7 +63,7 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
   static exception_ptr __from_native_exception_pointer(void*) _NOEXCEPT;
 
   template <class _Ep>
-  friend _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep) _NOEXCEPT;
+  friend _LIBCPP_HIDE_FROM_ABI exception_ptr __make_exception_ptr_explicit(_Ep&) _NOEXCEPT;
 
 public:
   // exception_ptr is basically a COW string.
@@ -89,25 +90,21 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
   friend _LIBCPP_EXPORTED_FROM_ABI void rethrow_exception(exception_ptr);
 };
 
-template <class _Ep>
-_LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep __e) _NOEXCEPT {
 #  if _LIBCPP_HAS_EXCEPTIONS
-#    if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION && __cplusplus >= 201103L
+#    if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
+template <class _Ep>
+_LIBCPP_HIDE_FROM_ABI exception_ptr __make_exception_ptr_explicit(_Ep& __e) _NOEXCEPT {
   using _Ep2 = __decay_t<_Ep>;
-
   void* __ex = __cxxabiv1::__cxa_allocate_exception(sizeof(_Ep));
 #      ifdef __wasm__
-  // In Wasm, a destructor returns its argument
-  (void)__cxxabiv1::__cxa_init_primary_exception(
-      __ex, const_cast<std::type_info*>(&typeid(_Ep)), [](void* __p) -> void* {
+  auto __cleanup = [](void* __p) -> void* {
+    std::__destroy_at(static_cast<_Ep2*>(__p));
+    return __p;
+  };
 #      else
-  (void)__cxxabiv1::__cxa_init_primary_exception(__ex, const_cast<std::type_info*>(&typeid(_Ep)), [](void* __p) {
-#      endif
-        std::__destroy_at(static_cast<_Ep2*>(__p));
-#      ifdef __wasm__
-        return __p;
+  auto __cleanup = [](void* __p) { std::__destroy_at(static_cast<_Ep2*>(__p)); };
 #      endif
-      });
+  (void)__cxxabiv1::__cxa_init_primary_exception(__ex, const_cast<std::type_info*>(&typeid(_Ep)), __cleanup);
 
   try {
     ::new (__ex) _Ep2(__e);
@@ -116,18 +113,47 @@ _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep __e) _NOEXCEPT {
     __cxxabiv1::__cxa_free_exception(__ex);
     return current_exception();
   }
-#    else
+}
+#    endif
+
+template <class _Ep>
+_LIBCPP_HIDE_FROM_ABI exception_ptr __make_exception_ptr_via_throw(_Ep& __e) _NOEXCEPT {
   try {
     throw __e;
   } catch (...) {
     return current_exception();
   }
+}
+
+template <class _Ep>
+_LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep __e) _NOEXCEPT {
+  // Objective-C exceptions are thrown via pointer. When throwing an Objective-C exception,
+  // Clang generates a call to `objc_exception_throw` instead of the usual `__cxa_throw`.
+  // That function creates an exception with a special Objective-C typeinfo instead of
+  // the usual C++ typeinfo, since that is needed to implement the behavior documented
+  // at [1]).
+  //
+  // Because of this special behavior, we can't create an exception via `__cxa_init_primary_exception`
+  // for Objective-C exceptions, otherwise we'd bypass `objc_exception_throw`. See https://llvm.org/PR135089.
+  //
+  // [1]:
+  // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Articles/Exceptions64Bit.html
+  if _LIBCPP_CONSTEXPR (is_pointer<_Ep>::value) {
+    return std::__make_exception_ptr_via_throw(__e);
+  }
+
+#    if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION && !defined(_LIBCPP_CXX03_LANG)
+  return std::__make_exception_ptr_explicit(__e);
+#    else
+  return std::__make_exception_ptr_via_throw(__e);
 #    endif
-#  else
-  ((void)__e);
+}
+#  else  // !_LIBCPP_HAS_EXCEPTIONS
+template <class _Ep>
+_LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep) _NOEXCEPT {
   std::abort();
-#  endif
 }
+#  endif // _LIBCPP_HAS_EXCEPTIONS
 
 #else // _LIBCPP_ABI_MICROSOFT
 
diff --git a/libcxx/test/std/language.support/support.exception/propagation/make_exception_ptr.objc.pass.mm b/libcxx/test/std/language.support/support.exception/propagation/make_exception_ptr.objc.pass.mm
new file mode 100644
index 0000000000000..05a6698ea1a59
--- /dev/null
+++ b/libcxx/test/std/language.support/support.exception/propagation/make_exception_ptr.objc.pass.mm
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// This test ensures that we can catch an Objective-C++ exception by type when
+// throwing an exception created via `std::make_exception_ptr`.
+// See http://llvm.org/PR135089.
+
+// UNSUPPORTED: no-exceptions
+// UNSUPPORTED: c++03
+
+// This test requires the Objective-C ARC, which is (only?) available on Darwin
+// out-of-the-box.
+// REQUIRES: has-fobjc-arc && darwin
+
+// ADDITIONAL_COMPILE_FLAGS: -fobjc-arc
+
+#include <cassert>
+#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 nullptr;
+}
+
+int main(int, char**) {
+  NSError* error         = [NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil];
+  std::exception_ptr exc = std::make_exception_ptr(error);
+  NSError* recov         = RecoverException(exc);
+  assert(recov != nullptr);
+
+  return 0;
+}

@github-project-automation github-project-automation bot moved this from Needs Triage to Needs Merge in LLVM Release Status Jul 8, 2025
@jyknight
Copy link
Member

jyknight commented Jul 8, 2025

@ldionne I think this seems fine to backport, but I do see that lots of tests are failing here. Might be pre-existing?...but someone needs to take a look at that...

@ldionne
Copy link
Member

ldionne commented Jul 8, 2025

So far, one issue seems legitimate (with C++11/C++14). I'll fix it, not sure why it wasn't an issue with the original patch. The other ones are unrelated and unfortunately they're caused by the infra moving on (e.g. version changes in the docker images).

@tstellar
Copy link
Collaborator

tstellar commented Jul 8, 2025

This came in pretty late, I'm not sure we are going to get this in. How critical is it, and are the test failures legitimate?

@ldionne
Copy link
Member

ldionne commented Jul 9, 2025

The CI failures are not legitimate, they are caused by changes in the infrastructure that make all patches on the release branch fail. This is something we should have fixed for upcoming releases, but it's always tricky to keep everything working on both branches.

About the patch itself, it's pretty important for Objective-C interoperability on Apple platforms. At the very least we're taking this fix in our downstream, but I understand if upstream would rather not have it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
Status: Needs Merge
Development

Successfully merging this pull request may close these issues.

5 participants