diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index d2420e9dda1..6613ca3a8ca 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -770,10 +770,18 @@ class BuiltinTest(unittest.TestCase): def __len__(self): return 4.5 self.assertRaises(TypeError, len, FloatLen()) + class NegativeLen: + def __len__(self): + return -10 + self.assertRaises(ValueError, len, NegativeLen()) class HugeLen: def __len__(self): return sys.maxsize + 1 self.assertRaises(OverflowError, len, HugeLen()) + class HugeNegativeLen: + def __len__(self): + return -sys.maxsize-10 + self.assertRaises(ValueError, len, HugeNegativeLen()) class NoLenMethod(object): pass self.assertRaises(TypeError, len, NoLenMethod()) diff --git a/Misc/NEWS b/Misc/NEWS index da40d8f5a00..5981b3a1c79 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.7.0 alpha 1? Core and Builtins ----------------- +- bpo-29839: len() now raises ValueError rather than OverflowError if + __len__() returned a large negative integer. + - bpo-11913: README.rst is now included in the list of distutils standard READMEs and therefore included in source distributions. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index ed50946a262..89fe82c7c39 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5924,14 +5924,21 @@ slot_sq_length(PyObject *self) if (res == NULL) return -1; - len = PyNumber_AsSsize_t(res, PyExc_OverflowError); - Py_DECREF(res); - if (len < 0) { - if (!PyErr_Occurred()) - PyErr_SetString(PyExc_ValueError, - "__len__() should return >= 0"); + + Py_SETREF(res, PyNumber_Index(res)); + if (res == NULL) + return -1; + + assert(PyLong_Check(res)); + if (Py_SIZE(res) < 0) { + PyErr_SetString(PyExc_ValueError, + "__len__() should return >= 0"); return -1; } + + len = PyNumber_AsSsize_t(res, PyExc_OverflowError); + assert(len >= 0 || PyErr_ExceptionMatches(PyExc_OverflowError)); + Py_DECREF(res); return len; }