gh-145492: Fix defaultdict __repr__ infinite recursion#145659
gh-145492: Fix defaultdict __repr__ infinite recursion#145659encukou merged 5 commits intopython:mainfrom
Conversation
Move Py_ReprLeave(dd->default_factory) inside the else branch so it is only called when Py_ReprEnter returned 0 (successfully entered). When Py_ReprEnter detects recursion (returns > 0), it does not add a new entry to the repr tracking list. Calling Py_ReprLeave in that case incorrectly removed the entry from the outer (non-recursive) call, which allowed subsequent recursive calls to bypass the guard entirely, leading to infinite recursion. Includes a regression test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| @@ -0,0 +1,3 @@ | |||
| Fix infinite recursion in :class:`collections.defaultdict` ``__repr__`` | |||
| when a ``defaultdict`` contains itself. Based on analysis by KowalskiThomas | |||
| in :issue:`145492`. | |||
There was a problem hiding this comment.
| in :issue:`145492`. | |
| in :gh:`145492`. |
:issue: is for b.p.o issues.
There was a problem hiding this comment.
Fixed - changed to :gh:\145492``. Thanks for catching that!
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Why are there duplicate blurbs?
There was a problem hiding this comment.
Removed the duplicate - it was added by blurb_it after I'd already created one manually. Fixed in latest push.
Address review feedback from StanFromIreland: - Changed :issue:`145492` to :gh:`145492` (correct syntax for GitHub issues) - Removed duplicate NEWS blurb added by blurb_it Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
GH-145746 is a backport of this pull request to the 3.13 branch. |
|
GH-145747 is a backport of this pull request to the 3.14 branch. |
| return {} | ||
| def __repr__(self): | ||
| repr(dd) | ||
| return "ProblematicFactory()" |
There was a problem hiding this comment.
I hope I am not too late, but this test never triggers an infinite recursion in python 3.14 without the fix.
I suggest replacing this line with return f"ProblematicFactory for {repr(dd)}" as reported in the @KowalskiThomas issue.
There was a problem hiding this comment.
Oof. Thanks for the catch! I though I tested, but apparently I used the wrong code :/
@mvanhorn, why did you change this part of the reproducer?
@KowalskiThomas, now I regret not asking you to send your branch as a PR. Sorry! :(
If you want to get it over the finish line (alas, without the start), now's your chance. Or I can send a fix-up PR myself.
There was a problem hiding this comment.
Good catch by @YvesDup - I didn't realize I was weakening the reproducer. I've submitted a fix-up PR at #145761 that uses return f"ProblematicFactory for {repr(dd)}" to match the original reproducer. Happy to close it if @KowalskiThomas wants to pick it up instead.
Fixes #145492.
Summary
Fixed the recursion guard in
defdict_reprso thatPy_ReprLeaveis only called whenPy_ReprEntersuccessfully entered (returned 0). Previously,Py_ReprLeavewas called unconditionally, including whenPy_ReprEnterreturned > 0 (recursion detected). SincePy_ReprEnterdoes not add a new entry when it detects recursion, callingPy_ReprLeavein that case incorrectly removed the entry from the outer (non-recursive) call, allowing subsequent recursive calls to bypass the guard and cause infinite recursion.The fix is a 3-line change: move
Py_ReprLeave(dd->default_factory)inside theelsebranch of the recursion check.Test plan
test_repr_recursive_factoryregression test that reproduces the infinite recursion scenario from the issuetest_defaultdicttests continue to pass (13/13)This contribution was developed with AI assistance (Claude Code).
collections.defaultdict's__repr__can lead to infinite recursion #145492