From 0e21cc6cf820679439d72e3ebd06227ee2a085f9 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 27 Sep 2024 14:51:01 -0700 Subject: [PATCH] GH-124547: Clear instance dictionary if memory error occurs during object dealloc (GH-124627) --- Lib/test/test_class.py | 15 +++++++++++++++ ...2024-09-26-12-19-13.gh-issue-124547.P_SHfU.rst | 3 +++ Objects/dictobject.c | 11 +++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-12-19-13.gh-issue-124547.P_SHfU.rst diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 902f788edc2..d2b6a23cc3c 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -1,6 +1,7 @@ "Test the functionality of Python classes implementing operators." import unittest +import test.support testmeths = [ @@ -932,6 +933,20 @@ class TestInlineValues(unittest.TestCase): C.a = X() C.a = X() + def test_detach_materialized_dict_no_memory(self): + import _testcapi + class A: + def __init__(self): + self.a = 1 + self.b = 2 + a = A() + d = a.__dict__ + with test.support.catch_unraisable_exception() as ex: + _testcapi.set_nomemory(0, 1) + del a + self.assertEqual(ex.unraisable.exc_type, MemoryError) + with self.assertRaises(KeyError): + d["a"] if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-12-19-13.gh-issue-124547.P_SHfU.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-12-19-13.gh-issue-124547.P_SHfU.rst new file mode 100644 index 00000000000..1005c651849 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-12-19-13.gh-issue-124547.P_SHfU.rst @@ -0,0 +1,3 @@ +When deallocating an object with inline values whose ``__dict__`` is still +live: if memory allocation for the inline values fails, clear the +dictionary. Prevents an interpreter crash. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index f38ab1b2865..e50cf8bf178 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3930,13 +3930,13 @@ dict_copy_impl(PyDictObject *self) } /* Copies the values, but does not change the reference - * counts of the objects in the array. */ + * counts of the objects in the array. + * Return NULL, but does *not* set an exception on failure */ static PyDictValues * copy_values(PyDictValues *values) { PyDictValues *newvalues = new_values(values->capacity); if (newvalues == NULL) { - PyErr_NoMemory(); return NULL; } newvalues->size = values->size; @@ -7216,6 +7216,13 @@ _PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj) PyDictValues *values = copy_values(mp->ma_values); if (values == NULL) { + /* Out of memory. Clear the dict */ + PyInterpreterState *interp = _PyInterpreterState_GET(); + PyDictKeysObject *oldkeys = mp->ma_keys; + set_keys(mp, Py_EMPTY_KEYS); + dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(mp)); + STORE_USED(mp, 0); + PyErr_NoMemory(); return -1; } mp->ma_values = values;