Skip to content

Commit b24c5ed

Browse files
authored
Replace "Unknown internal error occurred" with a more helpful message. (#3982)
1 parent de4ba92 commit b24c5ed

File tree

4 files changed

+30
-14
lines changed

4 files changed

+30
-14
lines changed

include/pybind11/detail/type_caster_base.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -470,14 +470,16 @@ PYBIND11_NOINLINE bool isinstance_generic(handle obj, const std::type_info &tp)
470470
return isinstance(obj, type);
471471
}
472472

473-
PYBIND11_NOINLINE std::string error_string() {
474-
if (!PyErr_Occurred()) {
475-
PyErr_SetString(PyExc_RuntimeError, "Unknown internal error occurred");
476-
return "Unknown internal error occurred";
473+
PYBIND11_NOINLINE std::string error_string(const char *called) {
474+
error_scope scope; // Fetch error state (will be restored when this function returns).
475+
if (scope.type == nullptr) {
476+
if (called == nullptr) {
477+
called = "pybind11::detail::error_string()";
478+
}
479+
pybind11_fail("Internal error: " + std::string(called)
480+
+ " called while Python error indicator not set.");
477481
}
478482

479-
error_scope scope; // Preserve error state
480-
481483
PyErr_NormalizeException(&scope.type, &scope.value, &scope.trace);
482484
if (scope.trace != nullptr) {
483485
PyException_SetTraceback(scope.value, scope.trace);

include/pybind11/pytypes.h

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ T reinterpret_steal(handle h) {
360360
}
361361

362362
PYBIND11_NAMESPACE_BEGIN(detail)
363-
std::string error_string();
363+
std::string error_string(const char *called = nullptr);
364364
PYBIND11_NAMESPACE_END(detail)
365365

366366
#if defined(_MSC_VER)
@@ -375,20 +375,27 @@ PYBIND11_NAMESPACE_END(detail)
375375
/// python).
376376
class PYBIND11_EXPORT_EXCEPTION error_already_set : public std::runtime_error {
377377
public:
378-
/// Constructs a new exception from the current Python error indicator, if any. The current
378+
/// Constructs a new exception from the current Python error indicator. The current
379379
/// Python error indicator will be cleared.
380-
error_already_set() : std::runtime_error(detail::error_string()) {
380+
error_already_set() : std::runtime_error(detail::error_string("pybind11::error_already_set")) {
381381
PyErr_Fetch(&m_type.ptr(), &m_value.ptr(), &m_trace.ptr());
382382
}
383383

384+
/// WARNING: The GIL must be held when this copy constructor is invoked!
384385
error_already_set(const error_already_set &) = default;
385386
error_already_set(error_already_set &&) = default;
386387

388+
/// WARNING: This destructor needs to acquire the Python GIL. This can lead to
389+
/// crashes (undefined behavior) if the Python interpreter is finalizing.
387390
inline ~error_already_set() override;
388391

389-
/// Give the currently-held error back to Python, if any. If there is currently a Python error
390-
/// already set it is cleared first. After this call, the current object no longer stores the
391-
/// error variables (but the `.what()` string is still available).
392+
/// Restores the currently-held Python error (which will clear the Python error indicator first
393+
/// if already set). After this call, the current object no longer stores the error variables.
394+
/// NOTE: Any copies of this object may still store the error variables. Currently there is no
395+
// protection against calling restore() from multiple copies.
396+
/// NOTE: This member function will always restore the normalized exception, which may or may
397+
/// not be the original Python exception.
398+
/// WARNING: The GIL must be held when this member function is called!
392399
void restore() {
393400
PyErr_Restore(m_type.release().ptr(), m_value.release().ptr(), m_trace.release().ptr());
394401
}
@@ -405,6 +412,7 @@ class PYBIND11_EXPORT_EXCEPTION error_already_set : public std::runtime_error {
405412
}
406413
/// An alternate version of `discard_as_unraisable()`, where a string provides information on
407414
/// the location of the error. For example, `__func__` could be helpful.
415+
/// WARNING: The GIL must be held when this member function is called!
408416
void discard_as_unraisable(const char *err_context) {
409417
discard_as_unraisable(reinterpret_steal<object>(PYBIND11_FROM_STRING(err_context)));
410418
}

tests/test_exceptions.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,10 @@ TEST_SUBMODULE(exceptions, m) {
228228
throw py::error_already_set();
229229
} catch (const std::runtime_error &e) {
230230
if ((err && e.what() != std::string("ValueError: foo"))
231-
|| (!err && e.what() != std::string("Unknown internal error occurred"))) {
231+
|| (!err
232+
&& e.what()
233+
!= std::string("Internal error: pybind11::error_already_set called "
234+
"while Python error indicator not set."))) {
232235
PyErr_Clear();
233236
throw std::runtime_error("error message mismatch");
234237
}

tests/test_exceptions.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ def test_std_exception(msg):
1616
def test_error_already_set(msg):
1717
with pytest.raises(RuntimeError) as excinfo:
1818
m.throw_already_set(False)
19-
assert msg(excinfo.value) == "Unknown internal error occurred"
19+
assert (
20+
msg(excinfo.value)
21+
== "Internal error: pybind11::error_already_set called while Python error indicator not set."
22+
)
2023

2124
with pytest.raises(ValueError) as excinfo:
2225
m.throw_already_set(True)

0 commit comments

Comments
 (0)