Skip to content

Commit 5e52976

Browse files
committed
gh-152083: Fix crash clearing a managed dict under low memory
1 parent 55a09ed commit 5e52976

3 files changed

Lines changed: 36 additions & 0 deletions

File tree

Lib/test/test_class.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,5 +1040,35 @@ def __init__(self):
10401040
self.assertFalse(out, msg=out.decode('utf-8'))
10411041
self.assertFalse(err, msg=err.decode('utf-8'))
10421042

1043+
@support.nomemtest
1044+
def test_clear_managed_dict_no_memory_keeps_exception(self):
1045+
# gh-152083: an exception may already be set when the managed dict is
1046+
# cleared under low memory. PyErr_FormatUnraisable() must not clear it.
1047+
code = """if 1:
1048+
import _testcapi
1049+
1050+
class A:
1051+
def __init__(self):
1052+
self.a = 1
1053+
self.b = 2
1054+
1055+
def f():
1056+
a = A()
1057+
a.__dict__
1058+
return [None] * 1000
1059+
1060+
for start in range(120):
1061+
_testcapi.set_nomemory(start)
1062+
try:
1063+
f()
1064+
except BaseException:
1065+
pass
1066+
_testcapi.remove_mem_hooks()
1067+
"""
1068+
rc, out, err = script_helper.assert_python_ok("-c", code)
1069+
self.assertEqual(rc, 0)
1070+
self.assertFalse(out, msg=out.decode('utf-8'))
1071+
self.assertFalse(err, msg=err.decode('utf-8'))
1072+
10431073
if __name__ == '__main__':
10441074
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash when an object's managed dictionary is cleared under low memory
2+
while an exception is set.

Objects/dictobject.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7944,6 +7944,9 @@ PyObject_ClearManagedDict(PyObject *obj)
79447944
// values. We need to materialize the keys. Nothing can modify
79457945
// this object, but we need to lock the dictionary.
79467946
int err;
7947+
// gh-152083: an exception may already be set, so keep it.
7948+
// The OOM path below reports via PyErr_FormatUnraisable() which clears it.
7949+
PyObject *exc = PyErr_GetRaisedException();
79477950
Py_BEGIN_CRITICAL_SECTION(dict);
79487951
err = detach_dict_from_object(dict, obj);
79497952
Py_END_CRITICAL_SECTION();
@@ -7963,6 +7966,7 @@ PyObject_ClearManagedDict(PyObject *obj)
79637966
clear_inline_values(_PyObject_InlineValues(obj));
79647967
Py_END_CRITICAL_SECTION();
79657968
}
7969+
PyErr_SetRaisedException(exc);
79667970
}
79677971
}
79687972
Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict);

0 commit comments

Comments
 (0)