Skip to content

Commit f7a90cf

Browse files
committed
Ensure that all translators are considered for nested exceptions
1 parent 6b9f653 commit f7a90cf

File tree

4 files changed

+121
-80
lines changed

4 files changed

+121
-80
lines changed

include/pybind11/detail/internals.h

+77-48
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,54 @@ struct internals {
211211
#endif
212212
};
213213

214+
internals &get_internals();
215+
216+
// the internals struct (above) is shared between all the modules. local_internals are only
217+
// for a single module. Any changes made to internals may require an update to
218+
// PYBIND11_INTERNALS_VERSION, breaking backwards compatibility. local_internals is, by design,
219+
// restricted to a single module. Whether a module has local internals or not should not
220+
// impact any other modules, because the only things accessing the local internals is the
221+
// module that contains them.
222+
struct local_internals {
223+
type_map<type_info *> registered_types_cpp;
224+
std::forward_list<ExceptionTranslator> registered_exception_translators;
225+
#if defined(WITH_THREAD) && PYBIND11_INTERNALS_VERSION == 4
226+
227+
// For ABI compatibility, we can't store the loader_life_support TLS key in
228+
// the `internals` struct directly. Instead, we store it in `shared_data` and
229+
// cache a copy in `local_internals`. If we allocated a separate TLS key for
230+
// each instance of `local_internals`, we could end up allocating hundreds of
231+
// TLS keys if hundreds of different pybind11 modules are loaded (which is a
232+
// plausible number).
233+
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
234+
235+
// Holds the shared TLS key for the loader_life_support stack.
236+
struct shared_loader_life_support_data {
237+
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
238+
shared_loader_life_support_data() {
239+
if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) {
240+
pybind11_fail("local_internals: could not successfully initialize the "
241+
"loader_life_support TLS key!");
242+
}
243+
}
244+
// We can't help but leak the TLS key, because Python never unloads extension modules.
245+
};
246+
247+
local_internals() {
248+
auto &internals = get_internals();
249+
// Get or create the `loader_life_support_stack_key`.
250+
auto &ptr = internals.shared_data["_life_support"];
251+
if (!ptr) {
252+
ptr = new shared_loader_life_support_data;
253+
}
254+
loader_life_support_tls_key
255+
= static_cast<shared_loader_life_support_data *>(ptr)->loader_life_support_tls_key;
256+
}
257+
#endif // defined(WITH_THREAD) && PYBIND11_INTERNALS_VERSION == 4
258+
};
259+
260+
local_internals &get_local_internals();
261+
214262
/// Additional type information which does not fit into the PyTypeObject.
215263
/// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`.
216264
struct type_info {
@@ -314,16 +362,41 @@ inline internals **&get_internals_pp() {
314362
return internals_pp;
315363
}
316364

317-
// forward decl
318-
inline void translate_exception(std::exception_ptr);
365+
// Apply all the extensions translators from a list
366+
// Return true if one of the translators completed without raising an exception
367+
// itself. Return of false indicates that if there are other translators
368+
// available, they should be tried.
369+
inline bool apply_exception_translators(std::forward_list<ExceptionTranslator> &translators,
370+
std::exception_ptr last_exception) {
371+
for (auto &translator : translators) {
372+
try {
373+
translator(last_exception);
374+
return true;
375+
} catch (...) {
376+
last_exception = std::current_exception();
377+
}
378+
}
379+
return false;
380+
}
381+
382+
inline bool apply_exception_translators(std::forward_list<ExceptionTranslator> &translators) {
383+
return apply_exception_translators(translators, std::current_exception());
384+
}
319385

320386
template <class T,
321387
enable_if_t<std::is_same<std::nested_exception, remove_cvref_t<T>>::value, int> = 0>
322388
bool handle_nested_exception(const T &exc, const std::exception_ptr &p) {
323389
std::exception_ptr nested = exc.nested_ptr();
324390
if (nested != nullptr && nested != p) {
325-
translate_exception(nested);
326-
return true;
391+
auto &local_translators = get_local_internals().registered_exception_translators;
392+
if (apply_exception_translators(local_translators, nested)) {
393+
return true;
394+
}
395+
396+
auto &translators = get_internals().registered_exception_translators;
397+
if (apply_exception_translators(translators, nested)) {
398+
return true;
399+
}
327400
}
328401
return false;
329402
}
@@ -491,50 +564,6 @@ PYBIND11_NOINLINE internals &get_internals() {
491564
return **internals_pp;
492565
}
493566

494-
// the internals struct (above) is shared between all the modules. local_internals are only
495-
// for a single module. Any changes made to internals may require an update to
496-
// PYBIND11_INTERNALS_VERSION, breaking backwards compatibility. local_internals is, by design,
497-
// restricted to a single module. Whether a module has local internals or not should not
498-
// impact any other modules, because the only things accessing the local internals is the
499-
// module that contains them.
500-
struct local_internals {
501-
type_map<type_info *> registered_types_cpp;
502-
std::forward_list<ExceptionTranslator> registered_exception_translators;
503-
#if defined(WITH_THREAD) && PYBIND11_INTERNALS_VERSION == 4
504-
505-
// For ABI compatibility, we can't store the loader_life_support TLS key in
506-
// the `internals` struct directly. Instead, we store it in `shared_data` and
507-
// cache a copy in `local_internals`. If we allocated a separate TLS key for
508-
// each instance of `local_internals`, we could end up allocating hundreds of
509-
// TLS keys if hundreds of different pybind11 modules are loaded (which is a
510-
// plausible number).
511-
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
512-
513-
// Holds the shared TLS key for the loader_life_support stack.
514-
struct shared_loader_life_support_data {
515-
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
516-
shared_loader_life_support_data() {
517-
if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) {
518-
pybind11_fail("local_internals: could not successfully initialize the "
519-
"loader_life_support TLS key!");
520-
}
521-
}
522-
// We can't help but leak the TLS key, because Python never unloads extension modules.
523-
};
524-
525-
local_internals() {
526-
auto &internals = get_internals();
527-
// Get or create the `loader_life_support_stack_key`.
528-
auto &ptr = internals.shared_data["_life_support"];
529-
if (!ptr) {
530-
ptr = new shared_loader_life_support_data;
531-
}
532-
loader_life_support_tls_key
533-
= static_cast<shared_loader_life_support_data *>(ptr)->loader_life_support_tls_key;
534-
}
535-
#endif // defined(WITH_THREAD) && PYBIND11_INTERNALS_VERSION == 4
536-
};
537-
538567
/// Works like `get_internals`, but for things which are locally registered.
539568
inline local_internals &get_local_internals() {
540569
// Current static can be created in the interpreter finalization routine. If the later will be

include/pybind11/pybind11.h

-18
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,6 @@ PYBIND11_WARNING_DISABLE_MSVC(4127)
5252

5353
PYBIND11_NAMESPACE_BEGIN(detail)
5454

55-
// Apply all the extensions translators from a list
56-
// Return true if one of the translators completed without raising an exception
57-
// itself. Return of false indicates that if there are other translators
58-
// available, they should be tried.
59-
inline bool apply_exception_translators(std::forward_list<ExceptionTranslator> &translators) {
60-
auto last_exception = std::current_exception();
61-
62-
for (auto &translator : translators) {
63-
try {
64-
translator(last_exception);
65-
return true;
66-
} catch (...) {
67-
last_exception = std::current_exception();
68-
}
69-
}
70-
return false;
71-
}
72-
7355
#if defined(_MSC_VER)
7456
# define PYBIND11_COMPAT_STRDUP _strdup
7557
#else

tests/test_exceptions.cpp

+22-6
Original file line numberDiff line numberDiff line change
@@ -297,19 +297,35 @@ TEST_SUBMODULE(exceptions, m) {
297297
}
298298
});
299299

300-
m.def("throw_nested_exception", []() {
300+
m.def("throw_stl_nested_exception", []() {
301301
try {
302-
throw std::runtime_error("Inner Exception");
302+
throw std::runtime_error("Inner STL Exception");
303303
} catch (const std::runtime_error &) {
304-
std::throw_with_nested(std::runtime_error("Outer Exception"));
304+
std::throw_with_nested(std::runtime_error("Outer STL Exception"));
305305
}
306306
});
307307

308-
m.def("throw_custom_nested_exception", []() {
308+
m.def("throw_stl_nested_exception_with_custom_exception", []() {
309309
try {
310-
throw std::runtime_error("Inner Exception");
310+
throw std::runtime_error("Inner STL Exception");
311311
} catch (const std::runtime_error &) {
312-
std::throw_with_nested(MyException5("Outer Exception"));
312+
std::throw_with_nested(MyException5("Outer Custom Exception"));
313+
}
314+
});
315+
316+
m.def("throw_custom_nested_exception_with_stl_exception", []() {
317+
try {
318+
throw MyException5("Inner Custom Exception");
319+
} catch (const MyException5 &) {
320+
std::throw_with_nested(std::runtime_error("Outer STL Exception"));
321+
}
322+
});
323+
324+
m.def("throw_custom_nested_exception_with_custom_exception", []() {
325+
try {
326+
throw MyException5("Inner Custom Exception");
327+
} catch (const MyException5 &) {
328+
std::throw_with_nested(MyException5("Outer Custom Exception"));
313329
}
314330
});
315331

tests/test_exceptions.py

+22-8
Original file line numberDiff line numberDiff line change
@@ -241,18 +241,32 @@ def pycatch(exctype, f, *args):
241241
assert str(excinfo.value) == "this is a helper-defined translated exception"
242242

243243

244-
def test_throw_nested_exception():
244+
def test_throw_stl_nested_exception():
245245
with pytest.raises(RuntimeError) as excinfo:
246-
m.throw_nested_exception()
247-
assert str(excinfo.value) == "Outer Exception"
248-
assert str(excinfo.value.__cause__) == "Inner Exception"
246+
m.throw_stl_nested_exception()
247+
assert str(excinfo.value) == "Outer STL Exception"
248+
assert str(excinfo.value.__cause__) == "Inner STL Exception"
249249

250250

251-
def test_throw_custom_nested_exception():
251+
def test_throw_stl_nested_exception_with_custom_exception():
252252
with pytest.raises(m.MyException5) as excinfo:
253-
m.throw_custom_nested_exception()
254-
assert str(excinfo.value) == "Outer Exception"
255-
assert str(excinfo.value.__cause__) == "Inner Exception"
253+
m.throw_stl_nested_exception_with_custom_exception()
254+
assert str(excinfo.value) == "Outer Custom Exception"
255+
assert str(excinfo.value.__cause__) == "Inner STL Exception"
256+
257+
258+
def test_throw_custom_nested_exception_with_stl_exception():
259+
with pytest.raises(RuntimeError) as excinfo:
260+
m.throw_custom_nested_exception_with_stl_exception()
261+
assert str(excinfo.value) == "Outer STL Exception"
262+
assert str(excinfo.value.__cause__) == "Inner Custom Exception"
263+
264+
265+
def test_throw_custom_nested_exception_with_custom_exception():
266+
with pytest.raises(m.MyException5) as excinfo:
267+
m.throw_custom_nested_exception_with_custom_exception()
268+
assert str(excinfo.value) == "Outer Custom Exception"
269+
assert str(excinfo.value.__cause__) == "Inner Custom Exception"
256270

257271

258272
# This can often happen if you wrap a pybind11 class in a Python wrapper

0 commit comments

Comments
 (0)