From 2ba570c81b1ac384ba6fa134cd5198d21c60cf40 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Tue, 10 Mar 2026 06:20:42 -0700 Subject: [PATCH 1/2] gh-145492: Fix defaultdict __repr__ infinite recursion (GH-145659) (cherry picked from commit 2d35f9bc1cf61b27639ed992dfbf363ab436fd8b) Co-authored-by: Matt Van Horn Co-Authored-By: Thomas Kowalski --- Lib/test/test_defaultdict.py | 15 +++++++++++++++ ...2026-03-09-00-00-00.gh-issue-145492.457Afc.rst | 3 +++ Modules/_collectionsmodule.c | 5 +++-- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-03-09-00-00-00.gh-issue-145492.457Afc.rst diff --git a/Lib/test/test_defaultdict.py b/Lib/test/test_defaultdict.py index fbd7354a915a0a..732e9a876ca8ad 100644 --- a/Lib/test/test_defaultdict.py +++ b/Lib/test/test_defaultdict.py @@ -204,5 +204,20 @@ def default_factory(): self.assertEqual(test_dict[key], 2) self.assertEqual(count, 2) + def test_repr_recursive_factory(self): + # gh-145492: defaultdict.__repr__ should not cause infinite recursion + # when the factory's __repr__ calls repr() on the defaultdict. + class ProblematicFactory: + def __call__(self): + return {} + def __repr__(self): + repr(dd) + return "ProblematicFactory()" + + dd = defaultdict(ProblematicFactory()) + # Should not raise RecursionError + r = repr(dd) + self.assertIn('ProblematicFactory()', r) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-03-09-00-00-00.gh-issue-145492.457Afc.rst b/Misc/NEWS.d/next/Library/2026-03-09-00-00-00.gh-issue-145492.457Afc.rst new file mode 100644 index 00000000000000..297ee4099f12c5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-09-00-00-00.gh-issue-145492.457Afc.rst @@ -0,0 +1,3 @@ +Fix infinite recursion in :class:`collections.defaultdict` ``__repr__`` +when a ``defaultdict`` contains itself. Based on analysis by KowalskiThomas +in :gh:`145492`. diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index f5fc0d02a16607..b2c3d0e42be4cb 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -2337,9 +2337,10 @@ defdict_repr(defdictobject *dd) } defrepr = PyUnicode_FromString("..."); } - else + else { defrepr = PyObject_Repr(dd->default_factory); - Py_ReprLeave(dd->default_factory); + Py_ReprLeave(dd->default_factory); + } } if (defrepr == NULL) { Py_DECREF(baserepr); From 1f6479862e2861811f5b94f6794169a4673b39e8 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Wed, 11 Mar 2026 14:02:23 +0100 Subject: [PATCH 2/2] gh-145492: fix regression test for defaultdict factory repr (GH-145788) --- Lib/test/test_defaultdict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_defaultdict.py b/Lib/test/test_defaultdict.py index 732e9a876ca8ad..a193eb10f16d17 100644 --- a/Lib/test/test_defaultdict.py +++ b/Lib/test/test_defaultdict.py @@ -212,12 +212,12 @@ def __call__(self): return {} def __repr__(self): repr(dd) - return "ProblematicFactory()" + return f"ProblematicFactory for {dd}" dd = defaultdict(ProblematicFactory()) # Should not raise RecursionError r = repr(dd) - self.assertIn('ProblematicFactory()', r) + self.assertIn("ProblematicFactory for", r) if __name__ == "__main__": unittest.main()