diff --git a/Include/abstract.h b/Include/abstract.h index b3e7030fa19..f4e31c77c0c 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -392,7 +392,7 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ /* Guess the size of object o using len(o) or o.__length_hint__(). If neither of those return a non-negative value, then return the - default value. This function never fails. All exceptions are cleared. + default value. If one of the calls fails, this function returns -1. */ PyAPI_FUNC(PyObject *) PyObject_GetItem(PyObject *o, PyObject *key); diff --git a/Lib/test/test_iterlen.py b/Lib/test/test_iterlen.py index 72e92a58013..2123501bcb5 100644 --- a/Lib/test/test_iterlen.py +++ b/Lib/test/test_iterlen.py @@ -195,6 +195,29 @@ class TestListReversed(TestInvariantWithoutMutations): d.extend(range(20)) self.assertEqual(len(it), 0) +## -- Check to make sure exceptions are not suppressed by __length_hint__() + + +class BadLen(object): + def __iter__(self): return iter(range(10)) + def __len__(self): + raise RuntimeError('hello') + +class BadLengthHint(object): + def __iter__(self): return iter(range(10)) + def __length_hint__(self): + raise RuntimeError('hello') + +class TestLengthHintExceptions(unittest.TestCase): + + def test_issue1242657(self): + self.assertRaises(RuntimeError, list, BadLen()) + self.assertRaises(RuntimeError, list, BadLengthHint()) + self.assertRaises(RuntimeError, [].extend, BadLen()) + self.assertRaises(RuntimeError, [].extend, BadLengthHint()) + b = bytearray(range(10)) + self.assertRaises(RuntimeError, b.extend, BadLen()) + self.assertRaises(RuntimeError, b.extend, BadLengthHint()) def test_main(): unittests = [ @@ -210,6 +233,7 @@ def test_main(): TestSet, TestList, TestListReversed, + TestLengthHintExceptions, ] support.run_unittest(*unittests) diff --git a/Misc/NEWS b/Misc/NEWS index 71f9ca19dbf..54260466306 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -17,6 +17,9 @@ Core and Builtins the type definition cmpfunc. The tp_compare slot has been renamed to tp_reserved, and is reserved for future usage. +- Issue 1242657: the __len__() and __length_hint__() calls in several tools + were suppressing all exceptions. These include list() and bytearray(). + - Issue #4707: round(x, n) now returns an integer if x is an integer. Previously it returned a float. diff --git a/Objects/abstract.c b/Objects/abstract.c index e42008a1488..86e2c394804 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -67,8 +67,8 @@ PyObject_Length(PyObject *o) /* The length hint function returns a non-negative value from o.__len__() or o.__length_hint__(). If those methods aren't found or return a negative - value, then the defaultvalue is returned. This function never fails. - Accordingly, it will mask exceptions raised in either method. + value, then the defaultvalue is returned. If one of the calls fails, + this function returns -1. */ Py_ssize_t @@ -82,29 +82,32 @@ _PyObject_LengthHint(PyObject *o, Py_ssize_t defaultvalue) rv = PyObject_Size(o); if (rv >= 0) return rv; - if (PyErr_Occurred()) + if (PyErr_Occurred()) { + if (!PyErr_ExceptionMatches(PyExc_TypeError) && + !PyErr_ExceptionMatches(PyExc_AttributeError)) + return -1; PyErr_Clear(); + } /* cache a hashed version of the attribute string */ if (hintstrobj == NULL) { hintstrobj = PyUnicode_InternFromString("__length_hint__"); if (hintstrobj == NULL) - goto defaultcase; + return -1; } /* try o.__length_hint__() */ ro = PyObject_CallMethodObjArgs(o, hintstrobj, NULL); - if (ro == NULL) - goto defaultcase; + if (ro == NULL) { + if (!PyErr_ExceptionMatches(PyExc_TypeError) && + !PyErr_ExceptionMatches(PyExc_AttributeError)) + return -1; + PyErr_Clear(); + return defaultvalue; + } rv = PyLong_AsSsize_t(ro); Py_DECREF(ro); - if (rv >= 0) - return rv; - -defaultcase: - if (PyErr_Occurred()) - PyErr_Clear(); - return defaultvalue; + return rv; } PyObject * @@ -1742,7 +1745,7 @@ PySequence_Tuple(PyObject *v) { PyObject *it; /* iter(v) */ Py_ssize_t n; /* guess for result tuple size */ - PyObject *result; + PyObject *result = NULL; Py_ssize_t j; if (v == NULL) @@ -1767,6 +1770,8 @@ PySequence_Tuple(PyObject *v) /* Guess result size and allocate space. */ n = _PyObject_LengthHint(v, 10); + if (n == -1) + goto Fail; result = PyTuple_New(n); if (result == NULL) goto Fail; diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 9e93ddaf37c..868013a8905 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -2616,6 +2616,10 @@ bytes_extend(PyByteArrayObject *self, PyObject *arg) /* Try to determine the length of the argument. 32 is abitrary. */ buf_size = _PyObject_LengthHint(arg, 32); + if (buf_size == -1) { + Py_DECREF(it); + return NULL; + } bytes_obj = PyByteArray_FromStringAndSize(NULL, buf_size); if (bytes_obj == NULL) diff --git a/Objects/listobject.c b/Objects/listobject.c index b8343631f44..2bab4ef26b3 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -802,6 +802,10 @@ listextend(PyListObject *self, PyObject *b) /* Guess a result list size. */ n = _PyObject_LengthHint(b, 8); + if (n == -1) { + Py_DECREF(it); + return NULL; + } m = Py_SIZE(self); mn = m + n; if (mn >= m) {