Skip to content

Commit

Permalink
use Python 3.12 PyErr_GetRaisedException() API
Browse files Browse the repository at this point in the history
  • Loading branch information
wjakob committed Jun 15, 2023
1 parent ed09b2b commit 36751cb
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 31 deletions.
2 changes: 2 additions & 0 deletions cmake/darwin-ld-cpython.sym
Expand Up @@ -132,6 +132,7 @@
-U _PyErr_FormatV
-U _PyErr_GetExcInfo
-U _PyErr_GetHandledException
-U _PyErr_GetRaisedException
-U _PyErr_GivenExceptionMatches
-U _PyErr_NewException
-U _PyErr_NewExceptionWithDoc
Expand Down Expand Up @@ -161,6 +162,7 @@
-U _PyErr_SetInterruptEx
-U _PyErr_SetNone
-U _PyErr_SetObject
-U _PyErr_SetRaisedException
-U _PyErr_SetString
-U _PyErr_SyntaxLocation
-U _PyErr_SyntaxLocationEx
Expand Down
2 changes: 1 addition & 1 deletion docs/api_core.rst
Expand Up @@ -1095,7 +1095,7 @@ the reference section on :ref:`class binding <class_binding>`.

Returns a handle to the exception value

.. cpp:function:: handle traceback() const
.. cpp:function:: object traceback() const

Returns a handle to the exception's traceback object

Expand Down
32 changes: 29 additions & 3 deletions include/nanobind/nb_error.h
Expand Up @@ -10,11 +10,21 @@
NAMESPACE_BEGIN(NB_NAMESPACE)

/// RAII wrapper that temporarily clears any Python error state
#if PY_VERSION_HEX >= 0x030C0000
struct error_scope {
error_scope() { value = PyErr_GetRaisedException(); }
~error_scope() { PyErr_SetRaisedException(value); }
private:
PyObject *value;
};
#else
struct error_scope {
error_scope() { PyErr_Fetch(&type, &value, &trace); }
~error_scope() { PyErr_Restore(type, value, trace); }
private:
PyObject *type, *value, *trace;
};
#endif

/// Wraps a Python error state as a C++ exception
class NB_EXPORT python_error : public std::exception {
Expand All @@ -25,7 +35,11 @@ class NB_EXPORT python_error : public std::exception {
NB_EXPORT_SHARED ~python_error() override;

bool matches(handle exc) const noexcept {
#if PY_VERSION_HEX < 0x030C0000
return PyErr_GivenExceptionMatches(m_type, exc.ptr()) != 0;
#else
return PyErr_GivenExceptionMatches(m_value, exc.ptr()) != 0;
#endif
}

/// Move the error back into the Python domain. This may only be called
Expand All @@ -42,16 +56,28 @@ class NB_EXPORT python_error : public std::exception {
PyErr_WriteUnraisable(context.ptr());
}

handle type() const { return m_type; }
handle value() const { return m_value; }
handle trace() const { return m_trace; }

#if PY_VERSION_HEX < 0x030C0000
handle type() const { return m_type; }
object traceback() const { return steal(m_traceback); }
#else
handle type() const { return value().type(); }
object traceback() const { return steal(PyException_GetTraceback(m_value)); }
#endif
[[deprecated]]
object trace() const { return traceback(); }

NB_EXPORT_SHARED const char *what() const noexcept override;

private:
#if PY_VERSION_HEX < 0x030C0000
mutable PyObject *m_type = nullptr;
mutable PyObject *m_value = nullptr;
mutable PyObject *m_trace = nullptr;
mutable PyObject *m_traceback = nullptr;
#else
mutable PyObject *m_value = nullptr;
#endif
mutable char *m_what = nullptr;
};

Expand Down
109 changes: 82 additions & 27 deletions src/error.cpp
Expand Up @@ -18,46 +18,102 @@ Buffer buf(128);

NAMESPACE_END(detail)

#if PY_VERSION_HEX >= 0x030C0000
python_error::python_error() {
PyErr_Fetch(&m_type, &m_value, &m_trace);
m_value = PyErr_GetRaisedException();
check(m_value,
"nanobind::python_error::python_error(): error indicator unset!");
}

python_error::~python_error() {
if (m_value) {
gil_scoped_acquire acq;
/* With GIL held */ {
// Clear error status in case the following executes Python code
error_scope scope;
Py_DECREF(m_value);
}
}

free(m_what);
}

python_error::python_error(const python_error &e)
: std::exception(e), m_value(e.m_value) {
if (m_value) {
gil_scoped_acquire acq;
Py_INCREF(m_value);
}
if (e.m_what)
m_what = NB_STRDUP(e.m_what);
}

python_error::python_error(python_error &&e) noexcept
: std::exception(e), m_value(e.m_value), m_what(e.m_what) {
e.m_value = nullptr;
e.m_what = nullptr;
}

void python_error::restore() noexcept {
check(m_value,
"nanobind::python_error::restore(): error was already restored!");

PyErr_SetRaisedException(m_value);
m_value = nullptr;
}

#else /* Exception handling for Python 3.11 and older versions */

python_error::python_error() {
PyErr_Fetch(&m_type, &m_value, &m_traceback);
check(m_type,
"nanobind::python_error::python_error(): error indicator unset!");
}

python_error::~python_error() {
if (m_type || m_value || m_trace) {
if (m_type) {
gil_scoped_acquire acq;
/* With GIL held */ {
// Clear error status in case the following executes Python code
error_scope scope;
Py_XDECREF(m_type);
Py_XDECREF(m_value);
Py_XDECREF(m_trace);
Py_XDECREF(m_type);
Py_XDECREF(m_traceback);
}
}
free(m_what);
}

python_error::python_error(const python_error &e)
: std::exception(e), m_type(e.m_type), m_value(e.m_value),
m_trace(e.m_trace) {
if (m_type || m_value || m_trace) {
m_traceback(e.m_traceback) {
if (m_type) {
gil_scoped_acquire acq;
Py_XINCREF(m_type);
Py_INCREF(m_type);
Py_XINCREF(m_value);
Py_XINCREF(m_trace);
Py_XINCREF(m_traceback);
}
if (e.m_what)
m_what = NB_STRDUP(e.m_what);
}

python_error::python_error(python_error &&e) noexcept
: std::exception(e), m_type(e.m_type), m_value(e.m_value),
m_trace(e.m_trace), m_what(e.m_what) {
e.m_type = e.m_value = e.m_trace = nullptr;
m_traceback(e.m_traceback), m_what(e.m_what) {
e.m_type = e.m_value = e.m_traceback = nullptr;
e.m_what = nullptr;
}

void python_error::restore() noexcept {
check(m_type,
"nanobind::python_error::restore(): error was already restored!");

PyErr_Restore(m_type, m_value, m_traceback);
m_type = m_value = m_traceback = nullptr;
}

#endif

const char *python_error::what() const noexcept {
using detail::buf;

Expand All @@ -71,23 +127,30 @@ const char *python_error::what() const noexcept {
if (m_what)
return m_what;

PyErr_NormalizeException(&m_type, &m_value, &m_trace);
#if PY_VERSION_HEX < 0x030C0000
PyErr_NormalizeException(&m_type, &m_value, &m_traceback);
check(m_type,
"nanobind::python_error::what(): PyNormalize_Exception() failed!");

if (m_trace) {
if (PyException_SetTraceback(m_value, m_trace) < 0)
if (m_traceback) {
if (PyException_SetTraceback(m_value, m_traceback) < 0)
PyErr_Clear();
}

handle exc_type = m_type, exc_value = m_value;
#else
handle exc_value = m_value, exc_type = exc_value.type();
#endif
object exc_traceback = traceback();

#if defined(Py_LIMITED_API) || defined(PYPY_VERSION)
object mod = module_::import_("traceback"),
result = mod.attr("format_exception")(handle(m_type), handle(m_value), handle(m_trace));
result = mod.attr("format_exception")(exc_type, exc_value, exc_traceback);
m_what = NB_STRDUP(borrow<str>(str("\n").attr("join")(result)).c_str());
#else
buf.clear();
if (m_trace) {
PyTracebackObject *to = (PyTracebackObject *) m_trace;
if (exc_traceback.is_valid()) {
PyTracebackObject *to = (PyTracebackObject *) exc_traceback.ptr();

// Get the deepest trace possible
while (to->tb_next)
Expand Down Expand Up @@ -130,28 +193,20 @@ const char *python_error::what() const noexcept {
}
}

if (m_type) {
object name = handle(m_type).attr("__name__");
if (exc_type.is_valid()) {
object name = exc_type.attr("__name__");
buf.put_dstr(borrow<str>(name).c_str());
buf.put(": ");
}

if (m_value)
if (exc_value.is_valid())
buf.put_dstr(str(m_value).c_str());
m_what = buf.copy();
#endif

return m_what;
}

void python_error::restore() noexcept {
check(m_type,
"nanobind::python_error::restore(): error was already restored!");

PyErr_Restore(m_type, m_value, m_trace);
m_type = m_value = m_trace = nullptr;
}

builtin_exception::builtin_exception(exception_type type, const char *what)
: std::runtime_error(what ? what : ""), m_type(type) { }
builtin_exception::~builtin_exception() { }
Expand Down

0 comments on commit 36751cb

Please sign in to comment.