Skip to content

Commit 2b784f8

Browse files
committed
Rewrite and simplify error_already_set
`error_already_set` is more complicated than it needs to be, partly because it manages reference counts itself rather than using `py::object`, and partly because it tries to do more exception clearing than is needed. This commit greatly simplifies it, and fixes pybind#927. Using `py::object` instead of `PyObject *` means we can rely on implicit copy/move constructors. The current logic did both a `PyErr_Clear` on deletion *and* a `PyErr_Fetch` on creation. I can't see how the `PyErr_Clear` on deletion is ever useful: the `Fetch` on creation itself clears the error, so the only way doing a `PyErr_Clear` on deletion could do anything if is some *other* exception was raised while the `error_already_set` object was alive--but in that case, clearing some other exception seems wrong. (Code that is worried about an exception handler raising another exception would already catch a second `error_already_set` from exception code). The destructor itself called `clear()`, but `clear()` was a little bit convoluted: it called `restore()` to restore the currently captured error, but then immediately cleared it. The only point seems to be because `PyErr_Restore` decrements the reference counts on the held objects, but we can simply do that manually. Or, actually, just *not* decrement them at all and let them be unreferenced (via the `py::object` destructor) when the `error_already_set` is itself destroyed. `clear()`, however, also had the side effect of clearing the current error, even if the current `error_already_set` didn't have a current error (e.g. because of a previous `restore()` or `clear()` call). I don't really see how clearing the error here can ever actually be useful: the only way the current error could be set is if you called `restore()` (in which case the current stored error-related members have already been released), or if some *other* code raised the error, in which case `clear()` on *this* object is clearing an error for which it shouldn't be responsible. Neither of those seem like intentional or desirable features, and manually requesting deletion of the stored references similarly seems pointless, so I've just made `clear()` an empty method and marked it deprecated.
1 parent cb3d406 commit 2b784f8

File tree

5 files changed

+26
-45
lines changed

5 files changed

+26
-45
lines changed

include/pybind11/common.h

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,6 @@ extern "C" {
226226
try { \
227227
return pybind11_init(); \
228228
} catch (pybind11::error_already_set &e) { \
229-
e.clear(); \
230229
PyErr_SetString(PyExc_ImportError, e.what()); \
231230
return nullptr; \
232231
} catch (const std::exception &e) { \
@@ -273,7 +272,6 @@ extern "C" {
273272
pybind11_init_##name(m); \
274273
return m.ptr(); \
275274
} catch (pybind11::error_already_set &e) { \
276-
e.clear(); \
277275
PyErr_SetString(PyExc_ImportError, e.what()); \
278276
return nullptr; \
279277
} catch (const std::exception &e) { \
@@ -348,8 +346,6 @@ inline static constexpr int log2(size_t n, int k = 0) { return (n <= 1) ? k : lo
348346
// Returns the size as a multiple of sizeof(void *), rounded up.
349347
inline static constexpr size_t size_in_ptrs(size_t s) { return 1 + ((s - 1) >> log2(sizeof(void *))); }
350348

351-
inline std::string error_string();
352-
353349
/**
354350
* The space to allocate for simple layout instance holders (see below) in multiple of the size of
355351
* a pointer (e.g. 2 means 16 bytes on 64-bit architectures). The default is the minimum required
@@ -698,36 +694,6 @@ template<typename T> T& get_or_create_shared_data(const std::string& name) {
698694
return *ptr;
699695
}
700696

701-
/// Fetch and hold an error which was already set in Python
702-
class error_already_set : public std::runtime_error {
703-
public:
704-
error_already_set() : std::runtime_error(detail::error_string()) {
705-
PyErr_Fetch(&type, &value, &trace);
706-
}
707-
708-
error_already_set(const error_already_set &) = delete;
709-
710-
error_already_set(error_already_set &&e)
711-
: std::runtime_error(e.what()), type(e.type), value(e.value),
712-
trace(e.trace) { e.type = e.value = e.trace = nullptr; }
713-
714-
inline ~error_already_set(); // implementation in pybind11.h
715-
716-
error_already_set& operator=(const error_already_set &) = delete;
717-
718-
/// Give the error back to Python
719-
void restore() { PyErr_Restore(type, value, trace); type = value = trace = nullptr; }
720-
721-
/// Clear the held Python error state (the C++ `what()` message remains intact)
722-
void clear() { restore(); PyErr_Clear(); }
723-
724-
/// Check if the trapped exception matches a given Python exception class
725-
bool matches(PyObject *ex) const { return PyErr_GivenExceptionMatches(ex, type); }
726-
727-
private:
728-
PyObject *type, *value, *trace;
729-
};
730-
731697
/// C++ bindings of builtin Python exceptions
732698
class builtin_exception : public std::runtime_error {
733699
public:

include/pybind11/embed.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
pybind11_init_##name(m); \
5252
return m.ptr(); \
5353
} catch (pybind11::error_already_set &e) { \
54-
e.clear(); \
5554
PyErr_SetString(PyExc_ImportError, e.what()); \
5655
return nullptr; \
5756
} catch (const std::exception &e) { \

include/pybind11/pybind11.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,13 +1745,6 @@ class gil_scoped_acquire { };
17451745
class gil_scoped_release { };
17461746
#endif
17471747

1748-
error_already_set::~error_already_set() {
1749-
if (value) {
1750-
gil_scoped_acquire gil;
1751-
clear();
1752-
}
1753-
}
1754-
17551748
inline function get_type_overload(const void *this_ptr, const detail::type_info *this_type, const char *name) {
17561749
handle self = detail::get_object_handle(this_ptr, this_type);
17571750
if (!self)

include/pybind11/pytypes.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,31 @@ template <typename T> T reinterpret_borrow(handle h) { return {h, object::borrow
279279
\endrst */
280280
template <typename T> T reinterpret_steal(handle h) { return {h, object::stolen_t{}}; }
281281

282+
NAMESPACE_BEGIN(detail)
283+
inline std::string error_string();
284+
NAMESPACE_END(detail)
285+
286+
/// Fetch and hold an error which was already set in Python
287+
class error_already_set : public std::runtime_error {
288+
public:
289+
error_already_set() : std::runtime_error(detail::error_string()) {
290+
PyErr_Fetch(&type.ptr(), &value.ptr(), &trace.ptr());
291+
}
292+
293+
/// Give the error back to Python
294+
void restore() { PyErr_Restore(type.release().ptr(), value.release().ptr(), trace.release().ptr()); }
295+
296+
/// Does nothing; provided for backwards compatibility.
297+
PYBIND11_DEPRECATED("Use of error_already_set.clear() is deprecated")
298+
void clear() {}
299+
300+
/// Check if the trapped exception matches a given Python exception class
301+
bool matches(handle ex) const { return PyErr_GivenExceptionMatches(ex.ptr(), type.ptr()); }
302+
303+
private:
304+
object type, value, trace;
305+
};
306+
282307
/** \defgroup python_builtins _
283308
Unless stated otherwise, the following C++ functions behave the same
284309
as their Python counterparts.

tests/test_exceptions.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,7 @@ void exception_matches() {
9393
foo["bar"];
9494
}
9595
catch (py::error_already_set& ex) {
96-
if (ex.matches(PyExc_KeyError))
97-
ex.clear();
98-
else
96+
if (!ex.matches(PyExc_KeyError))
9997
throw;
10098
}
10199
}

0 commit comments

Comments
 (0)