diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 4030716efb51f9..387841c7c90ab9 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -989,6 +989,18 @@ class MyDict(dict): pass self._tracked(MyDict()) + @support.cpython_only + def test_track_lazy_instance_dicts(self): + class C: + pass + o = C() + d = o.__dict__ + self._not_tracked(d) + o.untracked = 42 + self._not_tracked(d) + o.tracked = [] + self._tracked(d) + def make_shared_key_dict(self, n): class C: pass diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-10-48-31.gh-issue-133543.4jcszP.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-10-48-31.gh-issue-133543.4jcszP.rst new file mode 100644 index 00000000000000..046085892d45e6 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-07-10-48-31.gh-issue-133543.4jcszP.rst @@ -0,0 +1,2 @@ +Fix a possible memory leak that could occur when directly accessing instance +dictionaries (``__dict__``) that later become part of a reference cycle. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 680d6beb760571..17db279adc090d 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -6838,6 +6838,9 @@ store_instance_attr_lock_held(PyObject *obj, PyDictValues *values, value == NULL ? PyDict_EVENT_DELETED : PyDict_EVENT_MODIFIED); _PyDict_NotifyEvent(interp, event, dict, name, value); + if (value) { + MAINTAIN_TRACKING(dict, name, value); + } } FT_ATOMIC_STORE_PTR_RELEASE(values->values[ix], Py_XNewRef(value));