Issue #23571: PyObject_Call(), PyCFunction_Call() and call_function() now
raise a SystemError if a function returns a result and raises an exception.
The SystemError is chained to the previous exception.
Refactor also PyObject_Call() and PyCFunction_Call() to make them more readable.
Remove some checks which became useless (duplicate checks).
Change reviewed by Serhiy Storchaka.
diff --git a/Objects/abstract.c b/Objects/abstract.c
index 06e3382..ab13476 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -2073,37 +2073,70 @@
return PyEval_CallObjectWithKeywords(o, a, NULL);
}
+PyObject*
+_Py_CheckFunctionResult(PyObject *result, const char *func_name)
+{
+ int err_occurred = (PyErr_Occurred() != NULL);
+
+#ifdef NDEBUG
+ /* In debug mode: abort() with an assertion error. Use two different
+ assertions, so if an assertion fails, it's possible to know
+ if result was set or not and if an exception was raised or not. */
+ if (result != NULL)
+ assert(!err_occurred);
+ else
+ assert(err_occurred);
+#endif
+
+ if (result == NULL) {
+ if (!err_occurred) {
+ PyErr_Format(PyExc_SystemError,
+ "NULL result without error in %s", func_name);
+ return NULL;
+ }
+ }
+ else {
+ if (err_occurred) {
+ PyObject *exc, *val, *tb;
+ PyErr_Fetch(&exc, &val, &tb);
+
+ Py_DECREF(result);
+
+ PyErr_Format(PyExc_SystemError,
+ "result with error in %s", func_name);
+ _PyErr_ChainExceptions(exc, val, tb);
+ return NULL;
+ }
+ }
+ return result;
+}
+
PyObject *
PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
{
ternaryfunc call;
+ PyObject *result;
/* PyObject_Call() must not be called with an exception set,
because it may clear it (directly or indirectly) and so the
caller looses its exception */
assert(!PyErr_Occurred());
- if ((call = func->ob_type->tp_call) != NULL) {
- PyObject *result;
- if (Py_EnterRecursiveCall(" while calling a Python object"))
- return NULL;
- result = (*call)(func, arg, kw);
- Py_LeaveRecursiveCall();
-#ifdef NDEBUG
- if (result == NULL && !PyErr_Occurred()) {
- PyErr_SetString(
- PyExc_SystemError,
- "NULL result without error in PyObject_Call");
- }
-#else
- assert((result != NULL && !PyErr_Occurred())
- || (result == NULL && PyErr_Occurred()));
-#endif
- return result;
+ call = func->ob_type->tp_call;
+ if (call == NULL) {
+ PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
+ func->ob_type->tp_name);
+ return NULL;
}
- PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
- func->ob_type->tp_name);
- return NULL;
+
+ if (Py_EnterRecursiveCall(" while calling a Python object"))
+ return NULL;
+
+ result = (*call)(func, arg, kw);
+
+ Py_LeaveRecursiveCall();
+
+ return _Py_CheckFunctionResult(result, "PyObject_Call");
}
static PyObject*
diff --git a/Objects/methodobject.c b/Objects/methodobject.c
index 686baf9..85b413f 100644
--- a/Objects/methodobject.c
+++ b/Objects/methodobject.c
@@ -78,68 +78,71 @@
}
PyObject *
-PyCFunction_Call(PyObject *func, PyObject *arg, PyObject *kw)
+PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds)
{
-#define CHECK_RESULT(res) assert(res != NULL || PyErr_Occurred())
-
PyCFunctionObject* f = (PyCFunctionObject*)func;
PyCFunction meth = PyCFunction_GET_FUNCTION(func);
PyObject *self = PyCFunction_GET_SELF(func);
- PyObject *res;
+ PyObject *arg, *res;
Py_ssize_t size;
+ int flags;
- switch (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST)) {
- case METH_VARARGS:
- if (kw == NULL || PyDict_Size(kw) == 0) {
- res = (*meth)(self, arg);
- CHECK_RESULT(res);
- return res;
- }
- break;
- case METH_VARARGS | METH_KEYWORDS:
- res = (*(PyCFunctionWithKeywords)meth)(self, arg, kw);
- CHECK_RESULT(res);
- return res;
- case METH_NOARGS:
- if (kw == NULL || PyDict_Size(kw) == 0) {
- size = PyTuple_GET_SIZE(arg);
- if (size == 0) {
- res = (*meth)(self, NULL);
- CHECK_RESULT(res);
- return res;
- }
- PyErr_Format(PyExc_TypeError,
- "%.200s() takes no arguments (%zd given)",
- f->m_ml->ml_name, size);
- return NULL;
- }
- break;
- case METH_O:
- if (kw == NULL || PyDict_Size(kw) == 0) {
- size = PyTuple_GET_SIZE(arg);
- if (size == 1) {
- res = (*meth)(self, PyTuple_GET_ITEM(arg, 0));
- CHECK_RESULT(res);
- return res;
- }
- PyErr_Format(PyExc_TypeError,
- "%.200s() takes exactly one argument (%zd given)",
- f->m_ml->ml_name, size);
- return NULL;
- }
- break;
- default:
- PyErr_SetString(PyExc_SystemError, "Bad call flags in "
- "PyCFunction_Call. METH_OLDARGS is no "
- "longer supported!");
+ /* PyCFunction_Call() must not be called with an exception set,
+ because it may clear it (directly or indirectly) and so the
+ caller looses its exception */
+ assert(!PyErr_Occurred());
- return NULL;
+ flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);
+
+ if (flags == (METH_VARARGS | METH_KEYWORDS)) {
+ res = (*(PyCFunctionWithKeywords)meth)(self, args, kwds);
}
- PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
- f->m_ml->ml_name);
- return NULL;
+ else {
+ if (kwds != NULL && PyDict_Size(kwds) != 0) {
+ PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
+ f->m_ml->ml_name);
+ return NULL;
+ }
-#undef CHECK_RESULT
+ switch (flags) {
+ case METH_VARARGS:
+ res = (*meth)(self, args);
+ break;
+
+ case METH_NOARGS:
+ size = PyTuple_GET_SIZE(args);
+ if (size != 0) {
+ PyErr_Format(PyExc_TypeError,
+ "%.200s() takes no arguments (%zd given)",
+ f->m_ml->ml_name, size);
+ return NULL;
+ }
+
+ res = (*meth)(self, NULL);
+ break;
+
+ case METH_O:
+ size = PyTuple_GET_SIZE(args);
+ if (size != 1) {
+ PyErr_Format(PyExc_TypeError,
+ "%.200s() takes exactly one argument (%zd given)",
+ f->m_ml->ml_name, size);
+ return NULL;
+ }
+
+ arg = PyTuple_GET_ITEM(args, 0);
+ res = (*meth)(self, arg);
+ break;
+
+ default:
+ PyErr_SetString(PyExc_SystemError,
+ "Bad call flags in PyCFunction_Call. "
+ "METH_OLDARGS is no longer supported!");
+ return NULL;
+ }
+ }
+
+ return _Py_CheckFunctionResult(res, "PyCFunction_Call");
}
/* Methods (the standard built-in methods, that is) */