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
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 44 additions & 18 deletions libcxx/include/__exception/exception_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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.
Expand All @@ -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);
Expand All @@ -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_SINCE_CXX17 (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

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Loading