summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_weakref.py26
-rw-r--r--Objects/weakrefobject.c4
2 files changed, 29 insertions, 1 deletions
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
index 499ba77..a2f5b9b 100644
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -10,6 +10,7 @@ import copy
import threading
import time
import random
+import textwrap
from test import support
from test.support import script_helper, ALWAYS_EQ
@@ -1009,6 +1010,31 @@ class ReferencesTestCase(TestBase):
del x
support.gc_collect()
+ @support.cpython_only
+ def test_no_memory_when_clearing(self):
+ # gh-118331: Make sure we do not raise an exception from the destructor
+ # when clearing weakrefs if allocating the intermediate tuple fails.
+ code = textwrap.dedent("""
+ import _testcapi
+ import weakref
+
+ class TestObj:
+ pass
+
+ def callback(obj):
+ pass
+
+ obj = TestObj()
+ # The choice of 50 is arbitrary, but must be large enough to ensure
+ # the allocation won't be serviced by the free list.
+ wrs = [weakref.ref(obj, callback) for _ in range(50)]
+ _testcapi.set_nomemory(0)
+ del obj
+ """).strip()
+ res, _ = script_helper.run_python_until_end("-c", code)
+ stderr = res.err.decode("ascii", "backslashreplace")
+ self.assertNotRegex(stderr, "_Py_Dealloc: Deallocator of type 'TestObj'")
+
class SubclassableWeakrefTestCase(TestBase):
diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c
index 206107e..93c5fe3 100644
--- a/Objects/weakrefobject.c
+++ b/Objects/weakrefobject.c
@@ -1016,7 +1016,9 @@ PyObject_ClearWeakRefs(PyObject *object)
PyObject *exc = PyErr_GetRaisedException();
PyObject *tuple = PyTuple_New(num_weakrefs * 2);
if (tuple == NULL) {
- _PyErr_ChainExceptions1(exc);
+ _PyWeakref_ClearWeakRefsExceptCallbacks(object);
+ PyErr_WriteUnraisable(NULL);
+ PyErr_SetRaisedException(exc);
return;
}