From ab183ea69ee99569c5a37a2cc3ad87cfcd2c6a4d Mon Sep 17 00:00:00 2001 From: Kevin Mora <sci.kevinmora@gmail.com> Date: Mon, 29 Apr 2024 12:55:31 -0700 Subject: [PATCH 1/2] Prevent altering active exceptions in PyObject_ClearWeakRefs() and ensure weakrefs clearance on tuple allocation failure --- Objects/weakrefobject.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index 93c5fe3aacecfd..e1738f08b64528 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -1019,8 +1019,10 @@ PyObject_ClearWeakRefs(PyObject *object) _PyWeakref_ClearWeakRefsExceptCallbacks(object); PyErr_WriteUnraisable(NULL); PyErr_SetRaisedException(exc); + Py_XDECREF(exc); // Clean up the reference to the exception object return; } + Py_XDECREF(exc); // Clean up the reference if tuple creation is successful Py_ssize_t num_items = 0; for (int done = 0; !done;) { From c39648305fb79b71798bf32e0457119732678a58 Mon Sep 17 00:00:00 2001 From: Kevin Mora <sci.kevinmora@gmail.com> Date: Mon, 29 Apr 2024 13:11:49 -0700 Subject: [PATCH 2/2] Add exception handling for tuple operations, and create_bound_method --- Lib/test/test_weakref.py | 41 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index df90647baee31e..7d564c52594d64 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -42,7 +42,17 @@ def f(): pass return f def create_bound_method(): - return C().method + obj = C() + method = obj.method + try: + # Attempt to call the method through a bound method reference + result = method() + return result + except ReferenceError: + print("The object the method was bound to has been garbage collected.") + except Exception as e: + print(f"An error occurred: {e}") + return None class Object: @@ -1038,6 +1048,35 @@ def callback(obj): stderr = res.err.decode("ascii", "backslashreplace") self.assertNotRegex(stderr, "_Py_Dealloc: Deallocator of type 'TestObj'") + @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. + try: + wrs = [weakref.ref(obj, callback) for _ in range(50)] + _testcapi.set_nomemory(0) # Force memory allocation to fail + except MemoryError: + print('MemoryError handled properly') + 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'") + self.assertIn('MemoryError handled properly', stderr) + class SubclassableWeakrefTestCase(TestBase):