https://github.com/python/cpython/commit/946a0b69e217ff22a6c056047eab42053e9... commit: 946a0b69e217ff22a6c056047eab42053e9a2d5f branch: 3.6 author: Serhiy Storchaka <storchaka@gmail.com> committer: GitHub <noreply@github.com> date: 2017-08-03T12:14:35+03:00 summary: [3.6] bpo-31071: Avoid masking original TypeError in call with * unpacking (GH-2957) (#2991) when other arguments are passed. (cherry picked from commit 25e4f77) files: A Misc/NEWS.d/next/Core and Builtins/2017-07-31-13-28-53.bpo-31071.P9UBDy.rst M Lib/test/test_extcall.py M Python/ceval.c diff --git a/Lib/test/test_extcall.py b/Lib/test/test_extcall.py index 043df013112..2c1848337b2 100644 --- a/Lib/test/test_extcall.py +++ b/Lib/test/test_extcall.py @@ -163,6 +163,10 @@ Traceback (most recent call last): ... TypeError: myerror + >>> g(*range(1), *(broken() for i in range(1))) + Traceback (most recent call last): + ... + TypeError: myerror >>> class BrokenIterable1: ... def __iter__(self): @@ -172,6 +176,10 @@ Traceback (most recent call last): ... TypeError: myerror + >>> g(*range(1), *BrokenIterable1()) + Traceback (most recent call last): + ... + TypeError: myerror >>> class BrokenIterable2: ... def __iter__(self): @@ -182,6 +190,10 @@ Traceback (most recent call last): ... TypeError: myerror + >>> g(*range(1), *BrokenIterable2()) + Traceback (most recent call last): + ... + TypeError: myerror >>> class BrokenSequence: ... def __getitem__(self, idx): @@ -191,6 +203,10 @@ Traceback (most recent call last): ... TypeError: myerror + >>> g(*range(1), *BrokenSequence()) + Traceback (most recent call last): + ... + TypeError: myerror Make sure that the function doesn't stomp the dictionary diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-07-31-13-28-53.bpo-31071.P9UBDy.rst b/Misc/NEWS.d/next/Core and Builtins/2017-07-31-13-28-53.bpo-31071.P9UBDy.rst new file mode 100644 index 00000000000..c34d4751bfa --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2017-07-31-13-28-53.bpo-31071.P9UBDy.rst @@ -0,0 +1,2 @@ +Avoid masking original TypeError in call with * unpacking when other +arguments are passed. diff --git a/Python/ceval.c b/Python/ceval.c index 770dfcba89e..8eb78bf4d46 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -64,6 +64,8 @@ static void format_exc_unbound(PyCodeObject *co, int oparg); static PyObject * unicode_concatenate(PyObject *, PyObject *, PyFrameObject *, const _Py_CODEUNIT *); static PyObject * special_lookup(PyObject *, _Py_Identifier *); +static int check_args_iterable(PyObject *func, PyObject *vararg); +static void format_kwargs_mapping_error(PyObject *func, PyObject *kwargs); #define NAME_ERROR_MSG \ "name '%.200s' is not defined" @@ -2578,14 +2580,9 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) none_val = _PyList_Extend((PyListObject *)sum, PEEK(i)); if (none_val == NULL) { if (opcode == BUILD_TUPLE_UNPACK_WITH_CALL && - PyErr_ExceptionMatches(PyExc_TypeError)) { - PyObject *func = PEEK(1 + oparg); - PyErr_Format(PyExc_TypeError, - "%.200s%.200s argument after * " - "must be an iterable, not %.200s", - PyEval_GetFuncName(func), - PyEval_GetFuncDesc(func), - PEEK(i)->ob_type->tp_name); + PyErr_ExceptionMatches(PyExc_TypeError)) + { + check_args_iterable(PEEK(1 + oparg), PEEK(i)); } Py_DECREF(sum); goto error; @@ -2798,12 +2795,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) if (_PyDict_MergeEx(sum, arg, 2) < 0) { PyObject *func = PEEK(2 + oparg); if (PyErr_ExceptionMatches(PyExc_AttributeError)) { - PyErr_Format(PyExc_TypeError, - "%.200s%.200s argument after ** " - "must be a mapping, not %.200s", - PyEval_GetFuncName(func), - PyEval_GetFuncDesc(func), - arg->ob_type->tp_name); + format_kwargs_mapping_error(func, arg); } else if (PyErr_ExceptionMatches(PyExc_KeyError)) { PyObject *exc, *val, *tb; @@ -3370,13 +3362,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) * is not a mapping. */ if (PyErr_ExceptionMatches(PyExc_AttributeError)) { - func = SECOND(); - PyErr_Format(PyExc_TypeError, - "%.200s%.200s argument after ** " - "must be a mapping, not %.200s", - PyEval_GetFuncName(func), - PyEval_GetFuncDesc(func), - kwargs->ob_type->tp_name); + format_kwargs_mapping_error(SECOND(), kwargs); } Py_DECREF(kwargs); goto error; @@ -3389,14 +3375,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) callargs = POP(); func = TOP(); if (!PyTuple_CheckExact(callargs)) { - if (Py_TYPE(callargs)->tp_iter == NULL && - !PySequence_Check(callargs)) { - PyErr_Format(PyExc_TypeError, - "%.200s%.200s argument after * " - "must be an iterable, not %.200s", - PyEval_GetFuncName(func), - PyEval_GetFuncDesc(func), - callargs->ob_type->tp_name); + if (check_args_iterable(func, callargs) < 0) { Py_DECREF(callargs); goto error; } @@ -5351,6 +5330,32 @@ import_all_from(PyObject *locals, PyObject *v) return err; } +static int +check_args_iterable(PyObject *func, PyObject *args) +{ + if (args->ob_type->tp_iter == NULL && !PySequence_Check(args)) { + PyErr_Format(PyExc_TypeError, + "%.200s%.200s argument after * " + "must be an iterable, not %.200s", + PyEval_GetFuncName(func), + PyEval_GetFuncDesc(func), + args->ob_type->tp_name); + return -1; + } + return 0; +} + +static void +format_kwargs_mapping_error(PyObject *func, PyObject *kwargs) +{ + PyErr_Format(PyExc_TypeError, + "%.200s%.200s argument after ** " + "must be a mapping, not %.200s", + PyEval_GetFuncName(func), + PyEval_GetFuncDesc(func), + kwargs->ob_type->tp_name); +} + static void format_exc_check_arg(PyObject *exc, const char *format_str, PyObject *obj) {