Skip to content

Commit 36751cb

Browse files
committed
use Python 3.12 PyErr_GetRaisedException() API
1 parent ed09b2b commit 36751cb

File tree

4 files changed

+114
-31
lines changed

4 files changed

+114
-31
lines changed

cmake/darwin-ld-cpython.sym

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
-U _PyErr_FormatV
133133
-U _PyErr_GetExcInfo
134134
-U _PyErr_GetHandledException
135+
-U _PyErr_GetRaisedException
135136
-U _PyErr_GivenExceptionMatches
136137
-U _PyErr_NewException
137138
-U _PyErr_NewExceptionWithDoc
@@ -161,6 +162,7 @@
161162
-U _PyErr_SetInterruptEx
162163
-U _PyErr_SetNone
163164
-U _PyErr_SetObject
165+
-U _PyErr_SetRaisedException
164166
-U _PyErr_SetString
165167
-U _PyErr_SyntaxLocation
166168
-U _PyErr_SyntaxLocationEx

docs/api_core.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,7 @@ the reference section on :ref:`class binding <class_binding>`.
10951095

10961096
Returns a handle to the exception value
10971097

1098-
.. cpp:function:: handle traceback() const
1098+
.. cpp:function:: object traceback() const
10991099

11001100
Returns a handle to the exception's traceback object
11011101

include/nanobind/nb_error.h

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,21 @@
1010
NAMESPACE_BEGIN(NB_NAMESPACE)
1111

1212
/// RAII wrapper that temporarily clears any Python error state
13+
#if PY_VERSION_HEX >= 0x030C0000
14+
struct error_scope {
15+
error_scope() { value = PyErr_GetRaisedException(); }
16+
~error_scope() { PyErr_SetRaisedException(value); }
17+
private:
18+
PyObject *value;
19+
};
20+
#else
1321
struct error_scope {
1422
error_scope() { PyErr_Fetch(&type, &value, &trace); }
1523
~error_scope() { PyErr_Restore(type, value, trace); }
24+
private:
1625
PyObject *type, *value, *trace;
1726
};
27+
#endif
1828

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

2737
bool matches(handle exc) const noexcept {
38+
#if PY_VERSION_HEX < 0x030C0000
2839
return PyErr_GivenExceptionMatches(m_type, exc.ptr()) != 0;
40+
#else
41+
return PyErr_GivenExceptionMatches(m_value, exc.ptr()) != 0;
42+
#endif
2943
}
3044

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

45-
handle type() const { return m_type; }
4659
handle value() const { return m_value; }
47-
handle trace() const { return m_trace; }
60+
61+
#if PY_VERSION_HEX < 0x030C0000
62+
handle type() const { return m_type; }
63+
object traceback() const { return steal(m_traceback); }
64+
#else
65+
handle type() const { return value().type(); }
66+
object traceback() const { return steal(PyException_GetTraceback(m_value)); }
67+
#endif
68+
[[deprecated]]
69+
object trace() const { return traceback(); }
4870

4971
NB_EXPORT_SHARED const char *what() const noexcept override;
5072

5173
private:
74+
#if PY_VERSION_HEX < 0x030C0000
5275
mutable PyObject *m_type = nullptr;
5376
mutable PyObject *m_value = nullptr;
54-
mutable PyObject *m_trace = nullptr;
77+
mutable PyObject *m_traceback = nullptr;
78+
#else
79+
mutable PyObject *m_value = nullptr;
80+
#endif
5581
mutable char *m_what = nullptr;
5682
};
5783

src/error.cpp

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,46 +18,102 @@ Buffer buf(128);
1818

1919
NAMESPACE_END(detail)
2020

21+
#if PY_VERSION_HEX >= 0x030C0000
2122
python_error::python_error() {
22-
PyErr_Fetch(&m_type, &m_value, &m_trace);
23+
m_value = PyErr_GetRaisedException();
24+
check(m_value,
25+
"nanobind::python_error::python_error(): error indicator unset!");
26+
}
27+
28+
python_error::~python_error() {
29+
if (m_value) {
30+
gil_scoped_acquire acq;
31+
/* With GIL held */ {
32+
// Clear error status in case the following executes Python code
33+
error_scope scope;
34+
Py_DECREF(m_value);
35+
}
36+
}
37+
38+
free(m_what);
39+
}
40+
41+
python_error::python_error(const python_error &e)
42+
: std::exception(e), m_value(e.m_value) {
43+
if (m_value) {
44+
gil_scoped_acquire acq;
45+
Py_INCREF(m_value);
46+
}
47+
if (e.m_what)
48+
m_what = NB_STRDUP(e.m_what);
49+
}
50+
51+
python_error::python_error(python_error &&e) noexcept
52+
: std::exception(e), m_value(e.m_value), m_what(e.m_what) {
53+
e.m_value = nullptr;
54+
e.m_what = nullptr;
55+
}
56+
57+
void python_error::restore() noexcept {
58+
check(m_value,
59+
"nanobind::python_error::restore(): error was already restored!");
60+
61+
PyErr_SetRaisedException(m_value);
62+
m_value = nullptr;
63+
}
64+
65+
#else /* Exception handling for Python 3.11 and older versions */
66+
67+
python_error::python_error() {
68+
PyErr_Fetch(&m_type, &m_value, &m_traceback);
2369
check(m_type,
2470
"nanobind::python_error::python_error(): error indicator unset!");
2571
}
2672

2773
python_error::~python_error() {
28-
if (m_type || m_value || m_trace) {
74+
if (m_type) {
2975
gil_scoped_acquire acq;
3076
/* With GIL held */ {
3177
// Clear error status in case the following executes Python code
3278
error_scope scope;
33-
Py_XDECREF(m_type);
3479
Py_XDECREF(m_value);
35-
Py_XDECREF(m_trace);
80+
Py_XDECREF(m_type);
81+
Py_XDECREF(m_traceback);
3682
}
3783
}
3884
free(m_what);
3985
}
4086

4187
python_error::python_error(const python_error &e)
4288
: std::exception(e), m_type(e.m_type), m_value(e.m_value),
43-
m_trace(e.m_trace) {
44-
if (m_type || m_value || m_trace) {
89+
m_traceback(e.m_traceback) {
90+
if (m_type) {
4591
gil_scoped_acquire acq;
46-
Py_XINCREF(m_type);
92+
Py_INCREF(m_type);
4793
Py_XINCREF(m_value);
48-
Py_XINCREF(m_trace);
94+
Py_XINCREF(m_traceback);
4995
}
5096
if (e.m_what)
5197
m_what = NB_STRDUP(e.m_what);
5298
}
5399

54100
python_error::python_error(python_error &&e) noexcept
55101
: std::exception(e), m_type(e.m_type), m_value(e.m_value),
56-
m_trace(e.m_trace), m_what(e.m_what) {
57-
e.m_type = e.m_value = e.m_trace = nullptr;
102+
m_traceback(e.m_traceback), m_what(e.m_what) {
103+
e.m_type = e.m_value = e.m_traceback = nullptr;
58104
e.m_what = nullptr;
59105
}
60106

107+
void python_error::restore() noexcept {
108+
check(m_type,
109+
"nanobind::python_error::restore(): error was already restored!");
110+
111+
PyErr_Restore(m_type, m_value, m_traceback);
112+
m_type = m_value = m_traceback = nullptr;
113+
}
114+
115+
#endif
116+
61117
const char *python_error::what() const noexcept {
62118
using detail::buf;
63119

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

74-
PyErr_NormalizeException(&m_type, &m_value, &m_trace);
130+
#if PY_VERSION_HEX < 0x030C0000
131+
PyErr_NormalizeException(&m_type, &m_value, &m_traceback);
75132
check(m_type,
76133
"nanobind::python_error::what(): PyNormalize_Exception() failed!");
77134

78-
if (m_trace) {
79-
if (PyException_SetTraceback(m_value, m_trace) < 0)
135+
if (m_traceback) {
136+
if (PyException_SetTraceback(m_value, m_traceback) < 0)
80137
PyErr_Clear();
81138
}
82139

140+
handle exc_type = m_type, exc_value = m_value;
141+
#else
142+
handle exc_value = m_value, exc_type = exc_value.type();
143+
#endif
144+
object exc_traceback = traceback();
145+
83146
#if defined(Py_LIMITED_API) || defined(PYPY_VERSION)
84147
object mod = module_::import_("traceback"),
85-
result = mod.attr("format_exception")(handle(m_type), handle(m_value), handle(m_trace));
148+
result = mod.attr("format_exception")(exc_type, exc_value, exc_traceback);
86149
m_what = NB_STRDUP(borrow<str>(str("\n").attr("join")(result)).c_str());
87150
#else
88151
buf.clear();
89-
if (m_trace) {
90-
PyTracebackObject *to = (PyTracebackObject *) m_trace;
152+
if (exc_traceback.is_valid()) {
153+
PyTracebackObject *to = (PyTracebackObject *) exc_traceback.ptr();
91154

92155
// Get the deepest trace possible
93156
while (to->tb_next)
@@ -130,28 +193,20 @@ const char *python_error::what() const noexcept {
130193
}
131194
}
132195

133-
if (m_type) {
134-
object name = handle(m_type).attr("__name__");
196+
if (exc_type.is_valid()) {
197+
object name = exc_type.attr("__name__");
135198
buf.put_dstr(borrow<str>(name).c_str());
136199
buf.put(": ");
137200
}
138201

139-
if (m_value)
202+
if (exc_value.is_valid())
140203
buf.put_dstr(str(m_value).c_str());
141204
m_what = buf.copy();
142205
#endif
143206

144207
return m_what;
145208
}
146209

147-
void python_error::restore() noexcept {
148-
check(m_type,
149-
"nanobind::python_error::restore(): error was already restored!");
150-
151-
PyErr_Restore(m_type, m_value, m_trace);
152-
m_type = m_value = m_trace = nullptr;
153-
}
154-
155210
builtin_exception::builtin_exception(exception_type type, const char *what)
156211
: std::runtime_error(what ? what : ""), m_type(type) { }
157212
builtin_exception::~builtin_exception() { }

0 commit comments

Comments
 (0)