From 2a2e0a7a224473d60c6778c7e37df9e080b32877 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 16 Jul 2024 18:09:30 +0000 Subject: [PATCH 1/3] gh-121860: Fix crash when materializing managed dict The object's inline values may be marked invalid if the materialized dict was already initialized and then deleted. --- Objects/dictobject.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 149e552af3a729e..799345f6e5bfbfd 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -6683,11 +6683,18 @@ _PyObject_MaterializeManagedDict_LockHeld(PyObject *obj) { ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj); - PyDictValues *values = _PyObject_InlineValues(obj); - PyInterpreterState *interp = _PyInterpreterState_GET(); - PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj)); OBJECT_STAT_INC(dict_materialized_on_request); - PyDictObject *dict = make_dict_from_instance_attributes(interp, keys, values); + + PyDictValues *values = _PyObject_InlineValues(obj); + PyDictObject *dict; + if (values->valid) { + PyInterpreterState *interp = _PyInterpreterState_GET(); + PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj)); + dict = make_dict_from_instance_attributes(interp, keys, values); + } + else { + dict = PyDict_New(); + } FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict, (PyDictObject *)dict); return dict; From 74167e3168a7b389b46d57cd2a7fe7fbe76928f8 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 16 Jul 2024 18:24:20 +0000 Subject: [PATCH 2/3] Add blurb, test, and fix cast --- Lib/test/test_class.py | 18 ++++++++++++++++++ ...4-07-16-18-23-22.gh-issue-121860.-FTauD.rst | 1 + Objects/dictobject.c | 4 ++-- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-07-16-18-23-22.gh-issue-121860.-FTauD.rst diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 655d53b8d5bb6a6..8d56e33b4d875b8 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -882,6 +882,24 @@ class Foo: f.a = 3 self.assertEqual(f.a, 3) + def test_rematerialize_object_dict(self): + # gh-121860: re-materialize an object's managed dictionary after it + # had been deleted caused a crash. + class Foo: pass + f = Foo() + f.__dict__["attr"] = 1 + del f.__dict__ + + # Using a str subclass is a way to trigger the re-materialization + class StrSubclass(str): pass + self.assertFalse(hasattr(f, StrSubclass("attr"))) + + # Changing the __class__ also triggers the re-materialization + class Bar: pass + f.__class__ = Bar + self.assertIsInstance(f, Bar) + self.assertEqual(f.__dict__, {}) + def test_store_attr_type_cache(self): """Verifies that the type cache doesn't provide a value which is inconsistent from the dict.""" diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-07-16-18-23-22.gh-issue-121860.-FTauD.rst b/Misc/NEWS.d/next/Core and Builtins/2024-07-16-18-23-22.gh-issue-121860.-FTauD.rst new file mode 100644 index 000000000000000..a03ee83d6f8ec93 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-07-16-18-23-22.gh-issue-121860.-FTauD.rst @@ -0,0 +1 @@ +Fix crash when rematerializing a managed dictionary after it was deleted. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 799345f6e5bfbfd..ad921fcb113d912 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -6693,10 +6693,10 @@ _PyObject_MaterializeManagedDict_LockHeld(PyObject *obj) dict = make_dict_from_instance_attributes(interp, keys, values); } else { - dict = PyDict_New(); + dict = (PyDictObject *)PyDict_New(); } FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict, - (PyDictObject *)dict); + dict); return dict; } From 3bbc21d4a8139559bc141ac795441f9ec2d485ae Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 16 Jul 2024 18:27:38 +0000 Subject: [PATCH 3/3] Tweak wording --- Lib/test/test_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 8d56e33b4d875b8..d1f828b1ed824d7 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -883,7 +883,7 @@ class Foo: self.assertEqual(f.a, 3) def test_rematerialize_object_dict(self): - # gh-121860: re-materialize an object's managed dictionary after it + # gh-121860: rematerializing an object's managed dictionary after it # had been deleted caused a crash. class Foo: pass f = Foo()