diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index e5d3756355b98b1..80d8ae0beaccd0f 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 000000000000000..797d2de2240ef3c --- /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 6e43861d1630964..e2c5af9c67a8afe 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);