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.
This commit is contained in:
Victor Stinner 2015-03-06 23:35:27 +01:00
parent d81431f587
commit 4a7cc88472
7 changed files with 129 additions and 100 deletions

View file

@ -2073,37 +2073,70 @@ PyObject_CallObject(PyObject *o, PyObject *a)
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*

View file

@ -78,68 +78,71 @@ PyCFunction_GetFlags(PyObject *op)
}
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) */