summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Include/abstract.h2
-rw-r--r--Lib/test/test_iterlen.py31
-rw-r--r--Misc/NEWS4
-rw-r--r--Objects/abstract.c33
-rw-r--r--Objects/bytearrayobject.c4
-rw-r--r--Objects/listobject.c4
-rw-r--r--Python/bltinmodule.c2
7 files changed, 65 insertions, 15 deletions
diff --git a/Include/abstract.h b/Include/abstract.h
index 87afe9a..fc9c6b5 100644
--- a/Include/abstract.h
+++ b/Include/abstract.h
@@ -438,7 +438,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 2a32512..91873c2 100644
--- a/Lib/test/test_iterlen.py
+++ b/Lib/test/test_iterlen.py
@@ -195,6 +195,36 @@ class TestListReversed(TestInvariantWithoutMutations):
d.extend(xrange(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())
+ self.assertRaises(RuntimeError, zip, BadLen())
+ self.assertRaises(RuntimeError, zip, BadLengthHint())
+ self.assertRaises(RuntimeError, filter, None, BadLen())
+ self.assertRaises(RuntimeError, filter, None, BadLengthHint())
+ self.assertRaises(RuntimeError, map, chr, BadLen())
+ self.assertRaises(RuntimeError, map, chr, BadLengthHint())
+ b = bytearray(range(10))
+ self.assertRaises(RuntimeError, b.extend, BadLen())
+ self.assertRaises(RuntimeError, b.extend, BadLengthHint())
+
def test_main():
unittests = [
TestRepeat,
@@ -209,6 +239,7 @@ def test_main():
TestSet,
TestList,
TestListReversed,
+ TestLengthHintExceptions,
]
test_support.run_unittest(*unittests)
diff --git a/Misc/NEWS b/Misc/NEWS
index f399fde..4954c8b 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -14,6 +14,10 @@ Core and Builtins
- Issue #4978: Passing keyword arguments as unicode strings is now allowed.
+- Issue 1242657: the __len__() and __length_hint__() calls in several tools
+ were suppressing all exceptions. These include list(), filter(), map(),
+ zip(), and bytearray().
+
- os.ftruncate raises OSErrors instead of IOErrors for consistency with other os
functions.
diff --git a/Objects/abstract.c b/Objects/abstract.c
index 0d6aa4a..1ac4ac9 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -85,8 +85,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
@@ -100,29 +100,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 = PyString_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 = PyInt_AsLong(ro);
Py_DECREF(ro);
- if (rv >= 0)
- return rv;
-
-defaultcase:
- if (PyErr_Occurred())
- PyErr_Clear();
- return defaultvalue;
+ return rv;
}
PyObject *
@@ -2128,7 +2131,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)
@@ -2153,6 +2156,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 4b8d585..4cfd9f8 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -2691,6 +2691,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 7b4bf35..98d7e47 100644
--- a/Objects/listobject.c
+++ b/Objects/listobject.c
@@ -838,6 +838,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) {
diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
index 4e6f901..4d7dec1 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -268,6 +268,8 @@ builtin_filter(PyObject *self, PyObject *args)
/* Guess a result list size. */
len = _PyObject_LengthHint(seq, 8);
+ if (len == -1)
+ goto Fail_it;
/* Get a result list. */
if (PyList_Check(seq) && seq->ob_refcnt == 1) {