https://github.com/python/cpython/commit/2d4adac1448bfeddfecc769b58eb5e496d08313d
commit: 2d4adac1448bfeddfecc769b58eb5e496d08313d
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-10-21T11:50:55-07:00
summary:

[3.15] gh-159739: Release weak proxy operands on errors (GH-159968) (#160018)


(cherry picked from commit ebce4b70bf5e874c8602d02ab0ce709cb089c004)

Co-authored-by: Jelle Zijlstra <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-10-20-00-00-00.gh-issue-159739.bf569b.rst
M Lib/test/test_weakref.py
M Objects/weakrefobject.c

diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
index ccbb0d3f179053..ddfc2c395f9a5d 100644
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -605,6 +605,20 @@ class F(float):
         self.assertEqual(p + 1.0, 3.0)
         self.assertEqual(1.0 + p, 3.0)  # this used to SEGV
 
+    def test_dead_proxy_operand_does_not_leak(self):
+        dead = weakref.proxy(Object(2))
+        gc_collect()
+        for operation in (operator.add, operator.eq, lambda a, b: pow(a, 2, 
b)):
+            with self.subTest(operation=operation):
+                live = Object(1)
+                ref = weakref.ref(live)
+                proxy = weakref.proxy(live)
+                with self.assertRaises(ReferenceError):
+                    operation(proxy, dead)
+                del live
+                gc_collect()
+                self.assertIsNone(ref())
+
     def test_callbacks_protected(self):
         # Callbacks protected from already-set exceptions?
         # Regression test for SF bug #478534.
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-20-00-00-00.gh-issue-159739.bf569b.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-20-00-00-00.gh-issue-159739.bf569b.rst
new file mode 100644
index 00000000000000..797d2de2240ef3
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-20-00-00-00.gh-issue-159739.bf569b.rst
@@ -0,0 +1,2 @@
+Fix a reference leak in binary, ternary, and comparison operations on weak
+reference proxies when another operand is a dead proxy.
diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c
index c046bbbe9d536e..3e2c6c1c195ac1 100644
--- a/Objects/weakrefobject.c
+++ b/Objects/weakrefobject.c
@@ -536,19 +536,26 @@ proxy_check_ref(PyObject *obj)
 }
 
 
-/* If a parameter is a proxy, check that it is still "live" and wrap it,
- * replacing the original value with the raw object.  Raises ReferenceError
- * if the param is a dead proxy.
+/* Return a new reference to a live proxy's referent, or to the object itself
+ * if it is not a proxy. Raise ReferenceError for a dead proxy.
  */
+static PyObject *
+proxy_unwrap(PyObject *obj)
+{
+    if (PyWeakref_CheckProxy(obj)) {
+        PyObject *referent = _PyWeakref_GET_REF(obj);
+        if (!proxy_check_ref(referent)) {
+            return NULL;
+        }
+        return referent;
+    }
+    return Py_NewRef(obj);
+}
+
 #define UNWRAP(o) \
-        if (PyWeakref_CheckProxy(o)) { \
-            o = _PyWeakref_GET_REF(o); \
-            if (!proxy_check_ref(o)) { \
-                return NULL; \
-            } \
-        } \
-        else { \
-            Py_INCREF(o); \
+        o = proxy_unwrap(o); \
+        if (o == NULL) { \
+            return NULL; \
         }
 
 #define WRAP_UNARY(method, generic) \
@@ -564,7 +571,11 @@ proxy_check_ref(PyObject *obj)
     static PyObject * \
     method(PyObject *x, PyObject *y) { \
         UNWRAP(x); \
-        UNWRAP(y); \
+        y = proxy_unwrap(y); \
+        if (y == NULL) { \
+            Py_DECREF(x); \
+            return NULL; \
+        } \
         PyObject* res = generic(x, y); \
         Py_DECREF(x); \
         Py_DECREF(y); \
@@ -578,9 +589,18 @@ proxy_check_ref(PyObject *obj)
     static PyObject * \
     method(PyObject *proxy, PyObject *v, PyObject *w) { \
         UNWRAP(proxy); \
-        UNWRAP(v); \
+        v = proxy_unwrap(v); \
+        if (v == NULL) { \
+            Py_DECREF(proxy); \
+            return NULL; \
+        } \
         if (w != NULL) { \
-            UNWRAP(w); \
+            w = proxy_unwrap(w); \
+            if (w == NULL) { \
+                Py_DECREF(proxy); \
+                Py_DECREF(v); \
+                return NULL; \
+            } \
         } \
         PyObject* res = generic(proxy, v, w); \
         Py_DECREF(proxy); \
@@ -641,7 +661,11 @@ static PyObject *
 proxy_richcompare(PyObject *proxy, PyObject *v, int op)
 {
     UNWRAP(proxy);
-    UNWRAP(v);
+    v = proxy_unwrap(v);
+    if (v == NULL) {
+        Py_DECREF(proxy);
+        return NULL;
+    }
     PyObject* res = PyObject_RichCompare(proxy, v, op);
     Py_DECREF(proxy);
     Py_DECREF(v);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to