#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.