-
Notifications
You must be signed in to change notification settings - Fork 14.4k
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
base: release/20.x
Are you sure you want to change the base?
Conversation
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)
@jyknight What do you think about merging this PR to the release branch? |
@llvm/pr-subscribers-libcxx Author: None (llvmbot) ChangesBackport fcc09b6 Requested by: @ldionne Full diff: https://github.com/llvm/llvm-project/pull/147554.diff 2 Files Affected:
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;
+}
|
@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... |
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). |
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? |
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. |
Backport fcc09b6
Requested by: @ldionne