Skip to content

Commit fcc09b6

Browse files
itrofimowldionne
andauthored
[libc++] Fix std::make_exception_ptr interaction with ObjC (#135386)
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 #135089 Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
1 parent 02aacc4 commit fcc09b6

File tree

2 files changed

+88
-18
lines changed

2 files changed

+88
-18
lines changed

libcxx/include/__exception/exception_ptr.h

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <__memory/addressof.h>
1616
#include <__memory/construct_at.h>
1717
#include <__type_traits/decay.h>
18+
#include <__type_traits/is_pointer.h>
1819
#include <cstdlib>
1920
#include <typeinfo>
2021

@@ -62,7 +63,7 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
6263
static exception_ptr __from_native_exception_pointer(void*) _NOEXCEPT;
6364

6465
template <class _Ep>
65-
friend _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep) _NOEXCEPT;
66+
friend _LIBCPP_HIDE_FROM_ABI exception_ptr __make_exception_ptr_explicit(_Ep&) _NOEXCEPT;
6667

6768
public:
6869
// exception_ptr is basically a COW string so it is trivially relocatable.
@@ -91,25 +92,21 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
9192
friend _LIBCPP_EXPORTED_FROM_ABI void rethrow_exception(exception_ptr);
9293
};
9394

94-
template <class _Ep>
95-
_LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep __e) _NOEXCEPT {
9695
# if _LIBCPP_HAS_EXCEPTIONS
97-
# if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION && __cplusplus >= 201103L
96+
# if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
97+
template <class _Ep>
98+
_LIBCPP_HIDE_FROM_ABI exception_ptr __make_exception_ptr_explicit(_Ep& __e) _NOEXCEPT {
9899
using _Ep2 = __decay_t<_Ep>;
99-
100100
void* __ex = __cxxabiv1::__cxa_allocate_exception(sizeof(_Ep));
101101
# ifdef __wasm__
102-
// In Wasm, a destructor returns its argument
103-
(void)__cxxabiv1::__cxa_init_primary_exception(
104-
__ex, const_cast<std::type_info*>(&typeid(_Ep)), [](void* __p) -> void* {
102+
auto __cleanup = [](void* __p) -> void* {
103+
std::__destroy_at(static_cast<_Ep2*>(__p));
104+
return __p;
105+
};
105106
# else
106-
(void)__cxxabiv1::__cxa_init_primary_exception(__ex, const_cast<std::type_info*>(&typeid(_Ep)), [](void* __p) {
107-
# endif
108-
std::__destroy_at(static_cast<_Ep2*>(__p));
109-
# ifdef __wasm__
110-
return __p;
107+
auto __cleanup = [](void* __p) { std::__destroy_at(static_cast<_Ep2*>(__p)); };
111108
# endif
112-
});
109+
(void)__cxxabiv1::__cxa_init_primary_exception(__ex, const_cast<std::type_info*>(&typeid(_Ep)), __cleanup);
113110

114111
try {
115112
::new (__ex) _Ep2(__e);
@@ -118,18 +115,47 @@ _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep __e) _NOEXCEPT {
118115
__cxxabiv1::__cxa_free_exception(__ex);
119116
return current_exception();
120117
}
121-
# else
118+
}
119+
# endif
120+
121+
template <class _Ep>
122+
_LIBCPP_HIDE_FROM_ABI exception_ptr __make_exception_ptr_via_throw(_Ep& __e) _NOEXCEPT {
122123
try {
123124
throw __e;
124125
} catch (...) {
125126
return current_exception();
126127
}
128+
}
129+
130+
template <class _Ep>
131+
_LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep __e) _NOEXCEPT {
132+
// Objective-C exceptions are thrown via pointer. When throwing an Objective-C exception,
133+
// Clang generates a call to `objc_exception_throw` instead of the usual `__cxa_throw`.
134+
// That function creates an exception with a special Objective-C typeinfo instead of
135+
// the usual C++ typeinfo, since that is needed to implement the behavior documented
136+
// at [1]).
137+
//
138+
// Because of this special behavior, we can't create an exception via `__cxa_init_primary_exception`
139+
// for Objective-C exceptions, otherwise we'd bypass `objc_exception_throw`. See https://llvm.org/PR135089.
140+
//
141+
// [1]:
142+
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Articles/Exceptions64Bit.html
143+
if _LIBCPP_CONSTEXPR (is_pointer<_Ep>::value) {
144+
return std::__make_exception_ptr_via_throw(__e);
145+
}
146+
147+
# if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION && !defined(_LIBCPP_CXX03_LANG)
148+
return std::__make_exception_ptr_explicit(__e);
149+
# else
150+
return std::__make_exception_ptr_via_throw(__e);
127151
# endif
128-
# else
129-
((void)__e);
152+
}
153+
# else // !_LIBCPP_HAS_EXCEPTIONS
154+
template <class _Ep>
155+
_LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep) _NOEXCEPT {
130156
std::abort();
131-
# endif
132157
}
158+
# endif // _LIBCPP_HAS_EXCEPTIONS
133159

134160
#else // _LIBCPP_ABI_MICROSOFT
135161

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// This test ensures that we can catch an Objective-C++ exception by type when
10+
// throwing an exception created via `std::make_exception_ptr`.
11+
// See http://llvm.org/PR135089.
12+
13+
// UNSUPPORTED: no-exceptions
14+
// UNSUPPORTED: c++03
15+
16+
// This test requires the Objective-C ARC, which is (only?) available on Darwin
17+
// out-of-the-box.
18+
// REQUIRES: has-fobjc-arc && darwin
19+
20+
// ADDITIONAL_COMPILE_FLAGS: -fobjc-arc
21+
22+
#include <cassert>
23+
#include <exception>
24+
25+
#import <Foundation/Foundation.h>
26+
27+
NSError* RecoverException(const std::exception_ptr& exc) {
28+
try {
29+
std::rethrow_exception(exc);
30+
} catch (NSError* error) {
31+
return error;
32+
} catch (...) {
33+
}
34+
return nullptr;
35+
}
36+
37+
int main(int, char**) {
38+
NSError* error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil];
39+
std::exception_ptr exc = std::make_exception_ptr(error);
40+
NSError* recov = RecoverException(exc);
41+
assert(recov != nullptr);
42+
43+
return 0;
44+
}

0 commit comments

Comments
 (0)