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):