mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
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:
parent
d81431f587
commit
4a7cc88472
7 changed files with 129 additions and 100 deletions
|
@ -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*
|
||||
|
|
|
@ -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) */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue