diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 499ba77fd19..a2f5b9b2902 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 206107e8505..93c5fe3aace 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; }