From ddad6023890d64139bbda8fdbe929b48f4d88932 Mon Sep 17 00:00:00 2001 From: An Long Date: Sun, 5 Jul 2026 01:23:55 +0900 Subject: [PATCH 1/4] Fix weakref proxy ref leak --- Objects/weakrefobject.c | 72 ++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index daeba5368b21660..966838d89128056 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -541,23 +541,28 @@ 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. + * if the param is a dead proxy. Returns a new reference. */ -#define UNWRAP(o) \ - if (PyWeakref_CheckProxy(o)) { \ - o = _PyWeakref_GET_REF(o); \ - if (!proxy_check_ref(o)) { \ - return NULL; \ - } \ - } \ - else { \ - Py_INCREF(o); \ +static inline PyObject * +proxy_unwrap(PyObject *obj) +{ + if (PyWeakref_CheckProxy(obj)) { + obj = _PyWeakref_GET_REF(obj); + if (!proxy_check_ref(obj)) { + return NULL; } + return obj; + } + return Py_NewRef(obj); +} #define WRAP_UNARY(method, generic) \ static PyObject * \ method(PyObject *proxy) { \ - UNWRAP(proxy); \ + proxy = proxy_unwrap(proxy); \ + if (proxy == NULL) { \ + return NULL; \ + } \ PyObject* res = generic(proxy); \ Py_DECREF(proxy); \ return res; \ @@ -566,8 +571,15 @@ proxy_check_ref(PyObject *obj) #define WRAP_BINARY(method, generic) \ static PyObject * \ method(PyObject *x, PyObject *y) { \ - UNWRAP(x); \ - UNWRAP(y); \ + x = proxy_unwrap(x); \ + if (x == NULL) { \ + return NULL; \ + } \ + y = proxy_unwrap(y); \ + if (y == NULL) { \ + Py_DECREF(x); \ + return NULL; \ + } \ PyObject* res = generic(x, y); \ Py_DECREF(x); \ Py_DECREF(y); \ @@ -580,10 +592,22 @@ proxy_check_ref(PyObject *obj) #define WRAP_TERNARY(method, generic) \ static PyObject * \ method(PyObject *proxy, PyObject *v, PyObject *w) { \ - UNWRAP(proxy); \ - UNWRAP(v); \ + proxy = proxy_unwrap(proxy); \ + if (proxy == NULL) { \ + return NULL; \ + } \ + 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); \ @@ -595,7 +619,10 @@ proxy_check_ref(PyObject *obj) #define WRAP_METHOD(method, SPECIAL) \ static PyObject * \ method(PyObject *proxy, PyObject *Py_UNUSED(ignored)) { \ - UNWRAP(proxy); \ + proxy = proxy_unwrap(proxy); \ + if (proxy == NULL) { \ + return NULL; \ + } \ PyObject* res = PyObject_CallMethodNoArgs(proxy, &_Py_ID(SPECIAL)); \ Py_DECREF(proxy); \ return res; \ @@ -643,8 +670,15 @@ proxy_setattr(PyObject *proxy, PyObject *name, PyObject *value) static PyObject * proxy_richcompare(PyObject *proxy, PyObject *v, int op) { - UNWRAP(proxy); - UNWRAP(v); + proxy = proxy_unwrap(proxy); + if (proxy == NULL) { + return NULL; + } + 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); From 197e26de331ac4ab31d03fc54006a32a96dec81f Mon Sep 17 00:00:00 2001 From: An Long Date: Sun, 5 Jul 2026 11:42:51 +0900 Subject: [PATCH 2/4] Test weakref proxy ref leaks --- Lib/test/test_weakref.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index b9d1745592c09ca..90337762149051d 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -235,6 +235,42 @@ def check(proxy): self.assertRaises(ReferenceError, bool, ref3) self.assertEqual(self.cbcalled, 2) + @support.refcount_test + def test_proxy_dead_arg_refcount(self): + class C: + def __add__(self, other): + return NotImplemented + def __radd__(self, other): + return NotImplemented + def __getitem__(self, item): + return None + def __eq__(self, other): + return NotImplemented + def __pow__(self, other, modulo=None): + return NotImplemented + + dead_obj = C() + dead = weakref.proxy(dead_obj) + del dead_obj + gc_collect() + + cases = [ + lambda live: live + dead, + lambda live: live[dead], + lambda live: live == dead, + lambda live: pow(live, dead), + lambda live: pow(live, 2, dead), + ] + for case in cases: + with self.subTest(case=case): + live_obj = C() + live = weakref.proxy(live_obj) + refcount = sys.getrefcount(live_obj) + for _ in range(100): + with self.assertRaises(ReferenceError): + case(live) + self.assertEqual(sys.getrefcount(live_obj), refcount) + @support.cpython_only def test_proxy_repr(self): obj = C() From 0ddc08d7b3d94cd0bf2f280679e9bcc24b1e84db Mon Sep 17 00:00:00 2001 From: An Long Date: Sun, 5 Jul 2026 11:52:40 +0900 Subject: [PATCH 3/4] Add news entry --- .../next/Library/2026-07-05-11-52-30.gh-issue-153058.eE2OSL.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-05-11-52-30.gh-issue-153058.eE2OSL.rst diff --git a/Misc/NEWS.d/next/Library/2026-07-05-11-52-30.gh-issue-153058.eE2OSL.rst b/Misc/NEWS.d/next/Library/2026-07-05-11-52-30.gh-issue-153058.eE2OSL.rst new file mode 100644 index 000000000000000..b2a0f0396894ed5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-05-11-52-30.gh-issue-153058.eE2OSL.rst @@ -0,0 +1,2 @@ +Fix reference leaks in :func:`weakref.proxy` when binary, ternary, or +comparison operations fail because one of the operands is a dead proxy. From 8fdebd91ba20874a60e7402cd346aa06fb00cbb7 Mon Sep 17 00:00:00 2001 From: An Long Date: Wed, 8 Jul 2026 23:28:02 +0900 Subject: [PATCH 4/4] Use fail label in weakref ternary wrapper --- Objects/weakrefobject.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index 966838d89128056..0f48bfb231a83de 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -592,27 +592,26 @@ proxy_unwrap(PyObject *obj) #define WRAP_TERNARY(method, generic) \ static PyObject * \ method(PyObject *proxy, PyObject *v, PyObject *w) { \ + PyObject* res = NULL; \ proxy = proxy_unwrap(proxy); \ if (proxy == NULL) { \ return NULL; \ } \ v = proxy_unwrap(v); \ if (v == NULL) { \ - Py_DECREF(proxy); \ - return NULL; \ + goto fail; \ } \ if (w != NULL) { \ w = proxy_unwrap(w); \ if (w == NULL) { \ - Py_DECREF(proxy); \ - Py_DECREF(v); \ - return NULL; \ + goto fail; \ } \ } \ - PyObject* res = generic(proxy, v, w); \ - Py_DECREF(proxy); \ - Py_DECREF(v); \ + res = generic(proxy, v, w); \ Py_XDECREF(w); \ + fail: \ + Py_XDECREF(v); \ + Py_XDECREF(proxy); \ return res; \ }