﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
36810	SimpleLazyObject.__repr__ causes infinite recursion when setup function is a bound method	Sean Reed	Sean Reed	"= 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
"	Bug	closed	Core (Other)	6.0	Release blocker	fixed	LazyNonce, csp, SimpleLazyObject	Sean Reed Nilesh Pahari Clifford Gama	Ready for checkin	1	0	0	0	0	0
