diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index e401941c6d69700..51f99f9b0d492c9 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -1040,5 +1040,35 @@ def __init__(self): self.assertFalse(out, msg=out.decode('utf-8')) self.assertFalse(err, msg=err.decode('utf-8')) + @support.nomemtest + 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. + code = """if 1: + 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() + """ + rc, out, err = script_helper.assert_python_ok("-c", code) + self.assertEqual(rc, 0) + self.assertFalse(out, msg=out.decode('utf-8')) + self.assertFalse(err, msg=err.decode('utf-8')) + 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 000000000000000..b8d3149f7a5cd98 --- /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 6029205eac8b20f..eb559fdc6b373ef 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -7944,6 +7944,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(); @@ -7963,6 +7966,7 @@ PyObject_ClearManagedDict(PyObject *obj) clear_inline_values(_PyObject_InlineValues(obj)); Py_END_CRITICAL_SECTION(); } + PyErr_SetRaisedException(exc); } } Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict);