diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2015-03-06 22:35:27 (GMT) |
---|---|---|
committer | Victor Stinner <victor.stinner@gmail.com> | 2015-03-06 22:35:27 (GMT) |
commit | 4a7cc8847276df27c8f52987cda619ca279687c2 (patch) | |
tree | 9bc7cfee8bf0fc27dc7f14ba4853c935d9f21b4c /Objects | |
parent | d81431f587e9eab67db683908548b0ad46847b38 (diff) | |
download | cpython-4a7cc8847276df27c8f52987cda619ca279687c2.zip cpython-4a7cc8847276df27c8f52987cda619ca279687c2.tar.gz cpython-4a7cc8847276df27c8f52987cda619ca279687c2.tar.bz2 |
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.
Diffstat (limited to 'Objects')
-rw-r--r-- | Objects/abstract.c | 73 | ||||
-rw-r--r-- | Objects/methodobject.c | 101 |
2 files changed, 105 insertions, 69 deletions
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 @@ 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* 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 @@ 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); + /* 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()); + + flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST); + + if (flags == (METH_VARARGS | METH_KEYWORDS)) { + res = (*(PyCFunctionWithKeywords)meth)(self, args, kwds); + } + else { + if (kwds != NULL && PyDict_Size(kwds) != 0) { + PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments", + f->m_ml->ml_name); 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; + + 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; } - PyErr_Format(PyExc_TypeError, - "%.200s() takes exactly one argument (%zd given)", - f->m_ml->ml_name, size); + + 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; } - break; - default: - PyErr_SetString(PyExc_SystemError, "Bad call flags in " - "PyCFunction_Call. METH_OLDARGS is no " - "longer supported!"); - - return NULL; } - PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments", - f->m_ml->ml_name); - return NULL; -#undef CHECK_RESULT + return _Py_CheckFunctionResult(res, "PyCFunction_Call"); } /* Methods (the standard built-in methods, that is) */ |