diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py index a7658b5f476947..0410632e3f753e 100644 --- a/Lib/test/test_iter.py +++ b/Lib/test/test_iter.py @@ -2,6 +2,7 @@ import sys import unittest +import gc from test.support import run_unittest, cpython_only from test.support.os_helper import TESTFN, unlink from test.support import check_free_after_iterating, ALWAYS_EQ, NEVER_EQ @@ -1036,6 +1037,25 @@ def test_error_iter(self): self.assertRaises(TypeError, iter, typ()) self.assertRaises(ZeroDivisionError, iter, BadIterableClass()) + def test_access_result_tuple_while_iterating(self): + TAG = object() + + def monitor(): + lst = [x for x in gc.get_referrers(TAG) if isinstance(x, tuple)] + # This would be the result tuple if is accessible mid-iteration + t = lst[0] + print(t) + return t + + def my_iter(): + yield TAG + t = monitor() + breakpoint() + for x in range(10): + yield x + + self.assertRaises(IndexError, tuple, my_iter()) + def test_main(): run_unittest(TestCase) diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-02-12-00-56-06.bpo-15108.KKSAxV.rst b/Misc/NEWS.d/next/Core and Builtins/2021-02-12-00-56-06.bpo-15108.KKSAxV.rst new file mode 100644 index 00000000000000..d01828a059c41a --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-02-12-00-56-06.bpo-15108.KKSAxV.rst @@ -0,0 +1,3 @@ +Fixed an issue that could cause a crash when accessing an incomplete tuple +when collecting an iterable into a :class:`tuple` using :c:func:`PySequence_Tuple`. +Patch by Pablo Galindo. diff --git a/Objects/abstract.c b/Objects/abstract.c index 74a73ee469866d..3354b5558b8755 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1993,6 +1993,9 @@ PySequence_Tuple(PyObject *v) if (result == NULL) goto Fail; + // bpo-15108: Code can access the result tuple while being + // incomplete when calling PyIter_Next(). + PyObject_GC_UnTrack(result); /* Fill the tuple. */ for (j = 0; ; ++j) { PyObject *item = PyIter_Next(it); @@ -2022,10 +2025,17 @@ PySequence_Tuple(PyObject *v) Py_DECREF(item); goto Fail; } + // Resizing could track the tuple again + PyObject_GC_UnTrack(result); } PyTuple_SET_ITEM(result, j, item); } + // No more calls can go back into Python, so is safe + // to re-track the tuple. + + PyObject_GC_Track(result); + /* Cut tuple back if guess was too large. */ if (j < n && _PyTuple_Resize(&result, j) != 0)