[Django] #36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is a bound method

12 views
Skip to first unread message

Django

unread,
Dec 18, 2025, 6:11:37 PM12/18/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
---------------------------+-----------------------------------------
Reporter: sean-reed | Type: Uncategorized
Status: new | Component: Uncategorized
Version: 6.0 | Severity: Normal
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------+-----------------------------------------
= Bug Report: SimpleLazyObject.__repr__ causes infinite recursion when
setup function is a bound method =

== Summary ==

`SimpleLazyObject.__repr__` causes infinite recursion when the setup
function (`_setupfunc`) is a bound method of the lazy object instance.
This affects `LazyNonce` in the CSP middleware, making it impossible to
safely call `repr()` on unevaluated CSP nonces.

== Affected Version ==

Django 6.0 (likely also affects earlier versions with SimpleLazyObject,
but LazyNonce is new in 6.0)

== Minimal Reproduction ==

{{{#!python
from django.middleware.csp import LazyNonce

nonce = LazyNonce()
repr(nonce) # RecursionError: maximum recursion depth exceeded
}}}

Or without CSP imports:

{{{#!python
from django.utils.functional import SimpleLazyObject

class MyLazy(SimpleLazyObject):
def __init__(self):
super().__init__(self._generate) # bound method as setupfunc

def _generate(self):
return "value"

obj = MyLazy()
repr(obj) # RecursionError: maximum recursion depth exceeded
}}}

== Root Cause ==

`SimpleLazyObject.__repr__` (django/utils/functional.py) does:

{{{#!python
def __repr__(self):
if self._wrapped is empty:
repr_attr = self._setupfunc
else:
repr_attr = self._wrapped
return "<%s: %r>" % (type(self).__name__, repr_attr)
}}}

When `_setupfunc` is a bound method like `self._generate`, the `%r`
formatting calls `repr()` on the bound method. Python's bound method repr
includes a repr of the instance it's bound to:

{{{
<bound method MyLazy._generate of <MyLazy: ...>>
^^^^^^^^^^^
This calls MyLazy.__repr__ again
}}}

This creates infinite recursion:
1. `repr(obj)` → `SimpleLazyObject.__repr__`
2. `__repr__` formats `%r` on `self._setupfunc` (bound method)
3. Bound method repr includes `repr(self)`
4. Go to step 1

== Impact ==

Any code calling `repr()` on an unevaluated `LazyNonce` will crash:
* django-debug-toolbar's Templates panel (inspects template context)
* Debuggers (pdb, ipdb, IDE debuggers)
* Logging: `logger.debug("nonce: %r", nonce)`
* Error reporting tools (Sentry, etc.)
* Interactive shell inspection

== Suggested Fix ==

'''Option 1:''' Fix in `SimpleLazyObject.__repr__` to detect bound methods
of self:

{{{#!python
def __repr__(self):
if self._wrapped is empty:
# Avoid recursion if setupfunc is a bound method of self
if hasattr(self._setupfunc, '__self__') and
self._setupfunc.__self__ is self:
repr_attr = f"<bound method {self._setupfunc.__name__}>"
else:
repr_attr = self._setupfunc
else:
repr_attr = self._wrapped
return "<%s: %r>" % (type(self).__name__, repr_attr)
}}}

'''Option 2:''' Override `__repr__` in `LazyNonce`:

{{{#!python
class LazyNonce(SimpleLazyObject):
# ... existing code ...

def __repr__(self):
if self._wrapped is empty:
return f"<{type(self).__name__}: (unevaluated)>"
return f"<{type(self).__name__}: {self._wrapped!r}>"
}}}

'''Option 3:''' Use a lambda or staticmethod in `LazyNonce.__init__` to
avoid bound method:

{{{#!python
def __init__(self):
super().__init__(lambda: secrets.token_urlsafe(16))
}}}

== Environment ==

* Django 6.0
* Python 3.14.2
* Discovered via django-debug-toolbar 6.1.0 Templates panel
--
Ticket URL: <https://code.djangoproject.com/ticket/36810>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Dec 18, 2025, 6:19:16 PM12/18/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------+--------------------------------------
Reporter: Sean Reed | Owner: (none)
Type: Uncategorized | Status: new
Component: Uncategorized | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Changes (by Nilesh Pahari):

* cc: Nilesh Pahari (added)

--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:1>

Django

unread,
Dec 18, 2025, 6:40:16 PM12/18/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------+--------------------------------------
Reporter: sean-reed | Owner: (none)
Type: Uncategorized | Status: new
Component: Uncategorized | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Comment (by sean-reed):

Suggested fix (Option 1) available https://github.com/sean-
reed/django/tree/ticket_36810
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:2>

Django

unread,
Dec 18, 2025, 6:42:15 PM12/18/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------+--------------------------------------
Reporter: Sean Reed | Owner: (none)
Type: Uncategorized | Status: new
Component: Uncategorized | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Changes (by Sean Reed):

* has_patch: 0 => 1

--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:3>

Django

unread,
Dec 18, 2025, 6:54:31 PM12/18/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------+--------------------------------------
Reporter: Sean Reed | Owner: (none)
Type: Bug | Status: new
Component: Uncategorized | Version: 6.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Changes (by Sean Reed):

* type: Uncategorized => Bug

--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:4>

Django

unread,
Dec 19, 2025, 3:11:48 AM12/19/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: (none)
Type: Bug | Status: new
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Accepted
SimpleLazyObject |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Clifford Gama):

* component: Uncategorized => Core (Other)
* keywords: => LazyNonce, csp, SimpleLazyObject
* severity: Normal => Release blocker
* stage: Unreviewed => Accepted

Comment:

Thanks for the report! Reproduced for `LazyNonce`. Marking as release
blocker as csp is new in 6.0.

In the future, please avoid using LLMs to write overly verbose reports.
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:5>

Django

unread,
Dec 19, 2025, 7:19:43 AM12/19/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: (none)
Type: Bug | Status: new
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Accepted
SimpleLazyObject |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Natalia Bidart):

Thank you Sean for the ticket report and thank you Clifford for the
triage. I do have one question: the bug is in `SimpleLazyObject`, so my
initial thinking was that this is not a release blocker. Could you comment
on your rationale for marking as such? Are you thinking that we should
*also* have a defensive `__repr__` definition for `LazyNonce` as a
workaround and backport that to 6.0?
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:6>

Django

unread,
Dec 19, 2025, 8:18:03 AM12/19/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: (none)
Type: Bug | Status: new
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Accepted
SimpleLazyObject |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Clifford Gama):

* cc: Clifford Gama (added)

Comment:

I only marked as release blocker because the issue surfaced because of
`LazyNonce`'s usage of `SimpleLazyObject`'s `__init__()`, and I imagined
we'd have to do something about `LazyNonce.__repr__()`. But my knowledge
about backporting is a bit naive. Please feel free to adjust the severity
as you see fit.
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:7>

Django

unread,
Dec 19, 2025, 8:29:47 AM12/19/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: (none)
Type: Bug | Status: new
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Accepted
SimpleLazyObject |
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Natalia Bidart):

* needs_better_patch: 0 => 1
* needs_docs: 0 => 1

Comment:

Replying to [comment:7 Clifford Gama]:
> I only marked as release blocker because the issue surfaced because of
`LazyNonce`'s usage of `SimpleLazyObject`'s `__init__()`, and I imagined
we'd have to do something about `LazyNonce.__repr__()`. But my knowledge
about backporting is a bit naive. Please feel free to adjust the severity
as you see fit.

Thank you for the extra rationale!

I think we can leave the RB status set and ensure that the fixing PR has
two commits:

* one with the fix for the underlying cause in `SimpleLazyObject`
* one with a workaround for `LazyNone` with a release note so we backport
to 6.0

How does that sound?
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:8>

Django

unread,
Dec 19, 2025, 8:37:50 AM12/19/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: (none)
Type: Bug | Status: new
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Accepted
SimpleLazyObject |
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Clifford Gama):

Thanks! sounds good to me.
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:9>

Django

unread,
Dec 19, 2025, 8:58:27 AM12/19/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: (none)
Type: Bug | Status: new
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Accepted
SimpleLazyObject |
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Natalia Bidart):

Sean, can you please address the comments I added and create a new PR
targetting Django `main` branch?
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:10>

Django

unread,
Dec 19, 2025, 2:06:11 PM12/19/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: Sean Reed
Type: Bug | Status: assigned
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Accepted
SimpleLazyObject |
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* owner: (none) => Sean Reed
* status: new => assigned

--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:11>

Django

unread,
Dec 19, 2025, 4:18:33 PM12/19/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: Sean Reed
Type: Bug | Status: assigned
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Accepted
SimpleLazyObject |
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Sean Reed):

I've added the workaround for LazyNonce with release note to the PR as
requested (I also added a regression test specific to LazyNonce). Thanks
all.
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:12>

Django

unread,
Dec 22, 2025, 7:54:15 AM12/22/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: Sean Reed
Type: Bug | Status: assigned
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Accepted
SimpleLazyObject |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Natalia Bidart):

* needs_better_patch: 1 => 0
* needs_docs: 1 => 0

Comment:

Thank you Sean, please remember to update the ticket flags so the PR is
shown in the Review Queue. I'll do so now.
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:13>

Django

unread,
Dec 24, 2025, 10:09:07 AM12/24/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: Sean Reed
Type: Bug | Status: assigned
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Ready for
SimpleLazyObject | checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Natalia Bidart):

* stage: Accepted => Ready for checkin

--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:14>

Django

unread,
Dec 24, 2025, 12:46:50 PM12/24/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: Sean Reed
Type: Bug | Status: closed
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution: fixed
Keywords: LazyNonce, csp, | Triage Stage: Ready for
SimpleLazyObject | checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by nessita <124304+nessita@…>):

* resolution: => fixed
* status: assigned => closed

Comment:

In [changeset:"8e4b531111ddd3256c45eee601947e475651e8e7" 8e4b531]:
{{{#!CommitTicketReference repository=""
revision="8e4b531111ddd3256c45eee601947e475651e8e7"
Fixed #36810 -- Avoided infinite recursion in SimpleLazyObject.__repr__().

Detect when `SimpleLazyObject._setupfunc` is a bound method of the same
instance to use a safe representation and avoid infinite recursion.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:16>

Django

unread,
Dec 24, 2025, 12:46:50 PM12/24/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: Sean Reed
Type: Bug | Status: assigned
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution:
Keywords: LazyNonce, csp, | Triage Stage: Ready for
SimpleLazyObject | checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by nessita <124304+nessita@…>):

In [changeset:"165c3599965e63f88649a46fcc2ff681c52f2f66" 165c359]:
{{{#!CommitTicketReference repository=""
revision="165c3599965e63f88649a46fcc2ff681c52f2f66"
Refs #36810 -- Avoided infinite recursion in LazyNonce.__repr__().

Moved nonce generation in ``django.utils.csp.LazyNonce`` to a function
to avoid infinite recursion in ``SimpleLazyObject.__repr__`` for
unevaluated instances.

Co-authored-by: Natalia <124304+...@users.noreply.github.com>
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:15>

Django

unread,
Dec 24, 2025, 12:49:11 PM12/24/25
to django-...@googlegroups.com
#36810: SimpleLazyObject.__repr__ causes infinite recursion when setup function is
a bound method
-------------------------------------+-------------------------------------
Reporter: Sean Reed | Owner: Sean Reed
Type: Bug | Status: closed
Component: Core (Other) | Version: 6.0
Severity: Release blocker | Resolution: fixed
Keywords: LazyNonce, csp, | Triage Stage: Ready for
SimpleLazyObject | checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Natalia <124304+nessita@…>):

In [changeset:"16107ab710dc23d5ea0aa17da6bf29fe89b61bb0" 16107ab]:
{{{#!CommitTicketReference repository=""
revision="16107ab710dc23d5ea0aa17da6bf29fe89b61bb0"
[6.0.x] Refs #36810 -- Avoided infinite recursion in LazyNonce.__repr__().

Moved nonce generation in ``django.utils.csp.LazyNonce`` to a function
to avoid infinite recursion in ``SimpleLazyObject.__repr__`` for
unevaluated instances.

Co-authored-by: Natalia <124304+...@users.noreply.github.com>

Backport of 165c3599965e63f88649a46fcc2ff681c52f2f66 from main.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/36810#comment:17>
Reply all
Reply to author
Forward
0 new messages