Skip to content

Commit 18621e4

Browse files
committed
[CPyCppyy] Preserve Python errors when executing C++ function
1 parent 8847a84 commit 18621e4

File tree

1 file changed

+50
-38
lines changed

1 file changed

+50
-38
lines changed

bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -274,60 +274,72 @@ std::string CPyCppyy::CPPMethod::GetSignatureString(bool fa)
274274
//----------------------------------------------------------------------------
275275
void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg)
276276
{
277-
// helper to report errors in a consistent format (derefs msg)
278-
std::string details{};
277+
// Helper to report errors in a consistent format (derefs msg).
278+
//
279+
// Handles three cases:
280+
// 1. No Python error occured yet:
281+
// Set a new TypeError with the message "msg" and the docstring of this
282+
// C++ method to give some context.
283+
// 2. A C++ exception has occured:
284+
// Augment the exception message with the docstring of this method
285+
// 3. A Python exception has occured:
286+
// Do nothing, Python exceptions are already informative enough
287+
288+
#if PY_VERSION_HEX >= 0x030c0000
289+
PyObject *evalue = PyErr_Occurred() ? PyErr_GetRaisedException() : nullptr;
290+
PyObject *etype = evalue ? (PyObject *)Py_TYPE(evalue) : nullptr;
291+
#else
292+
PyObject *etype = nullptr;
293+
PyObject *evalue = nullptr;
294+
PyObject *etrace = nullptr;
279295

280-
PyObject *etype = nullptr, *evalue = nullptr;
281296
if (PyErr_Occurred()) {
282-
PyObject* etrace = nullptr;
283-
284297
PyErr_Fetch(&etype, &evalue, &etrace);
285-
286-
if (evalue) {
287-
PyObject* descr = PyObject_Str(evalue);
288-
if (descr) {
289-
details = CPyCppyy_PyText_AsString(descr);
290-
Py_DECREF(descr);
291-
}
292-
}
293-
294-
Py_XDECREF(etrace);
295298
}
299+
#endif
300+
301+
const bool isCppExc = evalue && PyType_IsSubtype((PyTypeObject*)etype, &CPPExcInstance_Type);
302+
// If the error is not a CPPExcInstance, the error from Python itself is
303+
// already complete and messing with it would only make it less informative.
304+
// Just restore and return.
305+
if (evalue && !isCppExc) {
306+
#if PY_VERSION_HEX >= 0x030c0000
307+
PyErr_SetRaisedException(evalue);
308+
#else
309+
PyErr_Restore(etype, evalue, etrace);
310+
#endif
311+
return;
312+
}
296313

297314
PyObject* doc = GetDocString();
298-
PyObject* errtype = etype;
299-
if (!errtype)
300-
errtype = PyExc_TypeError;
315+
const char* cdoc = CPyCppyy_PyText_AsString(doc);
316+
const char* cmsg = msg ? CPyCppyy_PyText_AsString(msg) : nullptr;
317+
PyObject* errtype = etype ? etype : PyExc_TypeError;
301318
PyObject* pyname = PyObject_GetAttr(errtype, PyStrings::gName);
302319
const char* cname = pyname ? CPyCppyy_PyText_AsString(pyname) : "Exception";
303320

304-
if (!PyType_IsSubtype((PyTypeObject*)errtype, &CPPExcInstance_Type)) {
305-
if (details.empty()) {
306-
PyErr_Format(errtype, "%s =>\n %s: %s", CPyCppyy_PyText_AsString(doc),
307-
cname, msg ? CPyCppyy_PyText_AsString(msg) : "");
308-
} else if (msg) {
309-
PyErr_Format(errtype, "%s =>\n %s: %s (%s)",
310-
CPyCppyy_PyText_AsString(doc), cname, CPyCppyy_PyText_AsString(msg),
311-
details.c_str());
312-
} else {
313-
PyErr_Format(errtype, "%s =>\n %s: %s",
314-
CPyCppyy_PyText_AsString(doc), cname, details.c_str());
315-
}
321+
if (!isCppExc) {
322+
// this is the case where no Python error has occured yet, and we set a new
323+
// error with context info
324+
PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, cmsg ? cmsg : "");
316325
} else {
317-
Py_XDECREF(((CPPExcInstance*)evalue)->fTopMessage);
326+
// augment the top message with context information
327+
PyObject *&topMessage = ((CPPExcInstance*)evalue)->fTopMessage;
328+
Py_XDECREF(topMessage);
318329
if (msg) {
319-
((CPPExcInstance*)evalue)->fTopMessage = CPyCppyy_PyText_FromFormat(\
320-
"%s =>\n %s: %s | ", CPyCppyy_PyText_AsString(doc), cname, CPyCppyy_PyText_AsString(msg));
330+
topMessage = CPyCppyy_PyText_FromFormat("%s =>\n %s: %s | ", cdoc, cname, cmsg);
321331
} else {
322-
((CPPExcInstance*)evalue)->fTopMessage = CPyCppyy_PyText_FromFormat(\
323-
"%s =>\n %s: ", CPyCppyy_PyText_AsString(doc), cname);
332+
topMessage = CPyCppyy_PyText_FromFormat("%s =>\n %s: ", cdoc, cname);
324333
}
325-
PyErr_SetObject(errtype, evalue);
334+
// restore the updated error
335+
#if PY_VERSION_HEX >= 0x030c0000
336+
PyErr_SetRaisedException(evalue);
337+
#else
338+
PyErr_Restore(etype, evalue, etrace);
339+
#endif
326340
}
327341

328342
Py_XDECREF(pyname);
329-
Py_XDECREF(evalue);
330-
Py_XDECREF(etype);
331343
Py_DECREF(doc);
332344
Py_XDECREF(msg);
333345
}

0 commit comments

Comments
 (0)