From 784e076a10e828f383282df8a4b993a1b821f547 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 15 Apr 2024 14:45:05 +0100 Subject: [PATCH] GH-117750: When clearing object's dict, clear inline values but leave dict attached (GH-117808) --- Lib/test/test_class.py | 10 ++++++ ...-04-12-11-19-18.gh-issue-117750.YttK6h.rst | 3 ++ Objects/dictobject.c | 33 ++++++++++--------- 3 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-04-12-11-19-18.gh-issue-117750.YttK6h.rst diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 4c181414273..a9cfd8df691 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -862,6 +862,16 @@ class TestInlineValues(unittest.TestCase): self.assertFalse(has_inline_values(c)) self.check_100(c) + def test_bug_117750(self): + "Aborted on 3.13a6" + class C: + def __init__(self): + self.__dict__.clear() + + obj = C() + self.assertEqual(obj.__dict__, {}) + obj.foo = None # Aborted here + self.assertEqual(obj.__dict__, {"foo":None}) if __name__ == '__main__': diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-12-11-19-18.gh-issue-117750.YttK6h.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-12-11-19-18.gh-issue-117750.YttK6h.rst new file mode 100644 index 00000000000..d7cf5d6e57d --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-12-11-19-18.gh-issue-117750.YttK6h.rst @@ -0,0 +1,3 @@ +Fix issue where an object's dict would get out of sync with the object's +internal values when being cleared. ``obj.__dict__.clear()`` now clears the +internal values, but leaves the dict attached to the object. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 9c38ef23146..003a03fd741 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -2681,25 +2681,28 @@ clear_lock_held(PyObject *op) interp, PyDict_EVENT_CLEARED, mp, NULL, NULL); // We don't inc ref empty keys because they're immortal ensure_shared_on_resize(mp); - - set_keys(mp, Py_EMPTY_KEYS); - set_values(mp, NULL); - mp->ma_used = 0; mp->ma_version_tag = new_version; - /* ...then clear the keys and values */ - if (oldvalues != NULL) { - if (!oldvalues->embedded) { - n = oldkeys->dk_nentries; - for (i = 0; i < n; i++) - Py_CLEAR(oldvalues->values[i]); - free_values(oldvalues, IS_DICT_SHARED(mp)); - } - dictkeys_decref(interp, oldkeys, false); - } - else { + mp->ma_used = 0; + if (oldvalues == NULL) { + set_keys(mp, Py_EMPTY_KEYS); assert(oldkeys->dk_refcnt == 1); dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(mp)); } + else { + n = oldkeys->dk_nentries; + for (i = 0; i < n; i++) { + Py_CLEAR(oldvalues->values[i]); + } + if (oldvalues->embedded) { + oldvalues->size = 0; + } + else { + set_values(mp, NULL); + set_keys(mp, Py_EMPTY_KEYS); + free_values(oldvalues, IS_DICT_SHARED(mp)); + dictkeys_decref(interp, oldkeys, false); + } + } ASSERT_CONSISTENT(mp); }