Skip to content

Commit f45cca3

Browse files
committed
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. (Python documentation suggests that `PyErr_Fetch` doesn't have to be matched with a `PyErr_Restore`). This updates the code to simply release the references on the three objects (preserving the gil acquire). `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. This also fixes a minor potential issue with the destruction: it is technically possible for `value` to be null (though this seems likely to be rare in practice); this updates the check to look at `type` which will always be non-null for a `Fetch`ed exception.
1 parent a4b63f7 commit f45cca3

File tree

5 files changed

+39
-40
lines changed

5 files changed

+39
-40
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,9 +1754,9 @@ class gil_scoped_release { };
17541754
#endif
17551755

17561756
error_already_set::~error_already_set() {
1757-
if (value) {
1757+
if (type) {
17581758
gil_scoped_acquire gil;
1759-
clear();
1759+
type.reset(); value.reset(); trace.reset();
17601760
}
17611761
}
17621762

include/pybind11/pytypes.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,42 @@ template <typename T> T reinterpret_borrow(handle h) { return {h, object::borrow
289289
\endrst */
290290
template <typename T> T reinterpret_steal(handle h) { return {h, object::stolen_t{}}; }
291291

292+
NAMESPACE_BEGIN(detail)
293+
inline std::string error_string();
294+
NAMESPACE_END(detail)
295+
296+
/// Fetch and hold an error which was already set in Python. An instance of this is typically
297+
/// thrown to propagate python-side errors back through C++ which can either be caught manually or
298+
/// else falls back to the function dispatcher (which then raises the captured error back to
299+
/// python).
300+
class error_already_set : public std::runtime_error {
301+
public:
302+
/// Constructs a new exception from the current Python error indicator, if any. The current
303+
/// Python error indicator will be cleared.
304+
error_already_set() : std::runtime_error(detail::error_string()) {
305+
PyErr_Fetch(&type.ptr(), &value.ptr(), &trace.ptr());
306+
}
307+
308+
inline ~error_already_set();
309+
310+
/// Give the currently-held error back to Python, if any. If there is currently a Python error
311+
/// already set it is cleared first. After this call, the current object no longer stores the
312+
/// error variables (but the `.what()` string is still available).
313+
void restore() { PyErr_Restore(type.release().ptr(), value.release().ptr(), trace.release().ptr()); }
314+
315+
// Does nothing; provided for backwards compatibility.
316+
PYBIND11_DEPRECATED("Use of error_already_set.clear() is deprecated")
317+
void clear() {}
318+
319+
/// Check if the currently trapped error type matches the given Python exception class (or a
320+
/// subclass thereof). May also be passed a tuple to search for any exception class matches in
321+
/// the given tuple.
322+
bool matches(handle ex) const { return PyErr_GivenExceptionMatches(ex.ptr(), type.ptr()); }
323+
324+
private:
325+
object type, value, trace;
326+
};
327+
292328
/** \defgroup python_builtins _
293329
Unless stated otherwise, the following C++ functions behave the same
294330
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)