diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 7bd6d966e8536b..bd7b78b188ed9a 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -1112,5 +1112,31 @@ def test_prepare_preserves_order(self): self.assertEqual(list(namespace), ['b', 'a']) + @support.nomemtest + @isolation.runInSubprocess() + def test_clear_managed_dict_no_memory_keeps_exception(self): + # gh-152083: an exception may already be set when the managed dict is + # cleared under low memory. PyErr_FormatUnraisable() must not clear it. + import _testcapi + + class A: + def __init__(self): + self.a = 1 + self.b = 2 + + def f(): + a = A() + a.__dict__ + return [None] * 1000 + + for start in range(120): + _testcapi.set_nomemory(start) + try: + f() + except BaseException: + pass + _testcapi.remove_mem_hooks() + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-03-22-32-58.gh-issue-152083.nSAuhO.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-03-22-32-58.gh-issue-152083.nSAuhO.rst new file mode 100644 index 00000000000000..b8d3149f7a5cd9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-03-22-32-58.gh-issue-152083.nSAuhO.rst @@ -0,0 +1,2 @@ +Fix a crash when an object's managed dictionary is cleared under low memory +while an exception is set. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 357e714de9a6c0..656b991055aefb 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -8006,6 +8006,9 @@ PyObject_ClearManagedDict(PyObject *obj) // values. We need to materialize the keys. Nothing can modify // this object, but we need to lock the dictionary. int err; + // gh-152083: an exception may already be set, so keep it. + // The OOM path below reports via PyErr_FormatUnraisable() which clears it. + PyObject *exc = PyErr_GetRaisedException(); Py_BEGIN_CRITICAL_SECTION(dict); err = detach_dict_from_object(dict, obj); Py_END_CRITICAL_SECTION(); @@ -8025,6 +8028,7 @@ PyObject_ClearManagedDict(PyObject *obj) clear_inline_values(_PyObject_InlineValues(obj)); Py_END_CRITICAL_SECTION(); } + PyErr_SetRaisedException(exc); } } Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict);