From a40d57366432cd65915b92fe3e6bfe1d5ad63be0 Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Mon, 12 Jan 2009 23:36:55 +0000 Subject: #3720: Interpreter crashes when an evil iterator removes its own next function. Now the slot is filled with a function that always raises. Will not backport: extensions compiled with 2.6.x would not run on 2.6.0. --- Include/abstract.h | 3 ++- Include/object.h | 1 + Lib/test/crashers/iter.py | 53 ----------------------------------------------- Lib/test/test_iter.py | 22 ++++++++++++++++++++ Misc/NEWS | 3 +++ Modules/itertoolsmodule.c | 11 ---------- Objects/abstract.c | 1 - Objects/object.c | 14 +++++++++++++ Objects/typeobject.c | 6 +++++- 9 files changed, 47 insertions(+), 67 deletions(-) delete mode 100644 Lib/test/crashers/iter.py diff --git a/Include/abstract.h b/Include/abstract.h index f3bc8bc..87afe9a 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -636,7 +636,8 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ #define PyIter_Check(obj) \ (PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \ - (obj)->ob_type->tp_iternext != NULL) + (obj)->ob_type->tp_iternext != NULL && \ + (obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented) PyAPI_FUNC(PyObject *) PyIter_Next(PyObject *); /* Takes an iterator object and calls its tp_iternext slot, diff --git a/Include/object.h b/Include/object.h index b02689c..c40d3620 100644 --- a/Include/object.h +++ b/Include/object.h @@ -473,6 +473,7 @@ PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *); PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *); PyAPI_FUNC(PyObject *) PyObject_SelfIter(PyObject *); +PyAPI_FUNC(PyObject *) _PyObject_NextNotImplemented(PyObject *); PyAPI_FUNC(PyObject *) PyObject_GenericGetAttr(PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_GenericSetAttr(PyObject *, PyObject *, PyObject *); diff --git a/Lib/test/crashers/iter.py b/Lib/test/crashers/iter.py deleted file mode 100644 index 081dcbc..0000000 --- a/Lib/test/crashers/iter.py +++ /dev/null @@ -1,53 +0,0 @@ -# Calls to PyIter_Next, or direct calls to tp_iternext, on an object -# which might no longer be an iterable because its 'next' method was -# removed. These are all variants of Issue3720. - -""" -Run this script with an argument between 1 and to test for -different crashes. -""" -N = 8 - -import sys - -class Foo(object): - def __iter__(self): - return self - def next(self): - del Foo.next - return (1, 2) - -def case1(): - list(enumerate(Foo())) - -def case2(): - x, y = Foo() - -def case3(): - filter(None, Foo()) - -def case4(): - map(None, Foo(), Foo()) - -def case5(): - max(Foo()) - -def case6(): - sum(Foo(), ()) - -def case7(): - dict(Foo()) - -def case8(): - sys.stdout.writelines(Foo()) - -# etc... - - -if __name__ == '__main__': - if len(sys.argv) < 2: - print __doc__.replace('', str(N)) - else: - n = int(sys.argv[1]) - func = globals()['case%d' % n] - func() diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py index f83de78..e847018 100644 --- a/Lib/test/test_iter.py +++ b/Lib/test/test_iter.py @@ -120,6 +120,13 @@ class TestCase(unittest.TestCase): def test_seq_class_iter(self): self.check_iterator(iter(SequenceClass(10)), range(10)) + # Test a new_style class with __iter__ but no next() method + def test_new_style_iter_class(self): + class IterClass(object): + def __iter__(self): + return self + self.assertRaises(TypeError, iter, IterClass()) + # Test two-argument iter() with callable instance def test_iter_callable(self): class C: @@ -877,6 +884,21 @@ class TestCase(unittest.TestCase): self.assertEqual(list(b), zip(range(5), range(5))) self.assertEqual(list(b), []) + def test_3720(self): + # Avoid a crash, when an iterator deletes its next() method. + class BadIterator(object): + def __iter__(self): + return self + def next(self): + del BadIterator.next + return 1 + + try: + for i in BadIterator() : + pass + except TypeError: + pass + def test_main(): run_unittest(TestCase) diff --git a/Misc/NEWS b/Misc/NEWS index 6a6f1e5..e39fe8a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1 Core and Builtins ----------------- +- Issue #3720: Fix a crash when an iterator modifies its class and removes its + __next__ method. + - Issue #4893: Use NT threading on CE. - Issue #4915: Port sysmodule to Windows CE. diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 5875d10..9d6dbd9 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -886,7 +886,6 @@ dropwhile_next(dropwhileobject *lz) long ok; PyObject *(*iternext)(PyObject *); - assert(PyIter_Check(it)); iternext = *Py_TYPE(it)->tp_iternext; for (;;) { item = iternext(it); @@ -1031,7 +1030,6 @@ takewhile_next(takewhileobject *lz) if (lz->stop == 1) return NULL; - assert(PyIter_Check(it)); item = (*Py_TYPE(it)->tp_iternext)(it); if (item == NULL) return NULL; @@ -1218,7 +1216,6 @@ islice_next(isliceobject *lz) Py_ssize_t oldnext; PyObject *(*iternext)(PyObject *); - assert(PyIter_Check(it)); iternext = *Py_TYPE(it)->tp_iternext; while (lz->cnt < lz->next) { item = iternext(it); @@ -1229,7 +1226,6 @@ islice_next(isliceobject *lz) } if (lz->stop != -1 && lz->cnt >= lz->stop) return NULL; - assert(PyIter_Check(it)); item = iternext(it); if (item == NULL) return NULL; @@ -1361,7 +1357,6 @@ starmap_next(starmapobject *lz) PyObject *result; PyObject *it = lz->it; - assert(PyIter_Check(it)); args = (*Py_TYPE(it)->tp_iternext)(it); if (args == NULL) return NULL; @@ -2577,7 +2572,6 @@ ifilter_next(ifilterobject *lz) long ok; PyObject *(*iternext)(PyObject *); - assert(PyIter_Check(it)); iternext = *Py_TYPE(it)->tp_iternext; for (;;) { item = iternext(it); @@ -2721,7 +2715,6 @@ ifilterfalse_next(ifilterfalseobject *lz) long ok; PyObject *(*iternext)(PyObject *); - assert(PyIter_Check(it)); iternext = *Py_TYPE(it)->tp_iternext; for (;;) { item = iternext(it); @@ -3059,7 +3052,6 @@ izip_next(izipobject *lz) Py_INCREF(result); for (i=0 ; i < tuplesize ; i++) { it = PyTuple_GET_ITEM(lz->ittuple, i); - assert(PyIter_Check(it)); item = (*Py_TYPE(it)->tp_iternext)(it); if (item == NULL) { Py_DECREF(result); @@ -3075,7 +3067,6 @@ izip_next(izipobject *lz) return NULL; for (i=0 ; i < tuplesize ; i++) { it = PyTuple_GET_ITEM(lz->ittuple, i); - assert(PyIter_Check(it)); item = (*Py_TYPE(it)->tp_iternext)(it); if (item == NULL) { Py_DECREF(result); @@ -3411,7 +3402,6 @@ izip_longest_next(iziplongestobject *lz) Py_INCREF(lz->fillvalue); item = lz->fillvalue; } else { - assert(PyIter_Check(it)); item = (*Py_TYPE(it)->tp_iternext)(it); if (item == NULL) { lz->numactive -= 1; @@ -3440,7 +3430,6 @@ izip_longest_next(iziplongestobject *lz) Py_INCREF(lz->fillvalue); item = lz->fillvalue; } else { - assert(PyIter_Check(it)); item = (*Py_TYPE(it)->tp_iternext)(it); if (item == NULL) { lz->numactive -= 1; diff --git a/Objects/abstract.c b/Objects/abstract.c index 956c4f4..80a1289 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -3067,7 +3067,6 @@ PyObject * PyIter_Next(PyObject *iter) { PyObject *result; - assert(PyIter_Check(iter)); result = (*iter->ob_type->tp_iternext)(iter); if (result == NULL && PyErr_Occurred() && diff --git a/Objects/object.c b/Objects/object.c index 1e0db4a..ba736a9 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1305,6 +1305,20 @@ PyObject_SelfIter(PyObject *obj) return obj; } +/* Helper used when the __next__ method is removed from a type: + tp_iternext is never NULL and can be safely called without checking + on every iteration. + */ + +PyObject * +_PyObject_NextNotImplemented(PyObject *self) +{ + PyErr_Format(PyExc_TypeError, + "'%.200s' object is not iterable", + Py_TYPE(self)->tp_name); + return NULL; +} + /* Generic GetAttr functions - put these in your tp_[gs]etattro slot */ PyObject * diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 3f790e8..8242242 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6090,8 +6090,12 @@ update_one_slot(PyTypeObject *type, slotdef *p) } do { descr = _PyType_Lookup(type, p->name_strobj); - if (descr == NULL) + if (descr == NULL) { + if (ptr == (void**)&type->tp_iternext) { + specific = _PyObject_NextNotImplemented; + } continue; + } if (Py_TYPE(descr) == &PyWrapperDescr_Type) { void **tptr = resolve_slotdups(type, p->name_strobj); if (tptr == NULL || tptr == ptr) -- cgit v0.12