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

0 views
Skip to first unread message

Django

unread,
Dec 18, 2025, 6:11:37 PM (11 hours ago) Dec 18
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 PM (11 hours ago) Dec 18
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 PM (11 hours ago) Dec 18
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 PM (11 hours ago) Dec 18
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 PM (10 hours ago) Dec 18
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,
3:11 AM (2 hours ago) 3:11 AM
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>
Reply all
Reply to author
Forward
0 new messages