From afc648acc8bb1459bb9078688c587e42273dfd7d Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Thu, 9 Apr 2026 23:18:06 +0530 Subject: [PATCH 01/11] fix unwrap leak with dead proxy operands --- Lib/test/test_weakref.py | 52 ++++++++++++++++++++++++++++++++++++++++ Objects/weakrefobject.c | 29 +++++++++++++++++++--- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index b187643e84521c..57790af43e274d 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -538,6 +538,58 @@ class MyObj: with self.assertRaises(TypeError): hash(weakref.proxy(obj)) + def test_proxy_unref_add_refcount(self): + class C: + def __add__(self, o): + return NotImplemented + + # create dead proxy + o = C() + dead = weakref.proxy(o) + del o + gc.collect() + + # create live proxy + obj = C() + ref = weakref.ref(obj) + proxy = weakref.proxy(obj) + + try: + operator.add(proxy, dead) + except ReferenceError: + pass + + del proxy, obj, dead + gc.collect() + + self.assertIsNone(ref(), "Leaked object in add operation") + + def test_proxy_unref_pow_refcount(self): + class C: + def __pow__(self, o, m=None): + return NotImplemented + + # create dead proxy + o = C() + dead = weakref.proxy(o) + del o + gc.collect() + + # create live proxy + obj = C() + ref = weakref.ref(obj) + proxy = weakref.proxy(obj) + + try: + pow(proxy, dead, None) + except ReferenceError: + pass + + del proxy, obj, dead + gc.collect() + + self.assertIsNone(ref(), "Leaked object in pow operation") + def test_getweakrefcount(self): o = C() ref1 = weakref.ref(o) diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index 61fa3ddad0bfd8..4b6a32bd102836 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -538,9 +538,7 @@ proxy_check_ref(PyObject *obj) #define UNWRAP(o) \ if (PyWeakref_CheckProxy(o)) { \ o = _PyWeakref_GET_REF(o); \ - if (!proxy_check_ref(o)) { \ - return NULL; \ - } \ + proxy_check_ref(o); \ } \ else { \ Py_INCREF(o); \ @@ -559,7 +557,13 @@ proxy_check_ref(PyObject *obj) static PyObject * \ method(PyObject *x, PyObject *y) { \ UNWRAP(x); \ + if (x == NULL) \ + return NULL; \ UNWRAP(y); \ + if (y == NULL) { \ + Py_XDECREF(x); \ + return NULL; \ + } \ PyObject* res = generic(x, y); \ Py_DECREF(x); \ Py_DECREF(y); \ @@ -573,9 +577,20 @@ proxy_check_ref(PyObject *obj) static PyObject * \ method(PyObject *proxy, PyObject *v, PyObject *w) { \ UNWRAP(proxy); \ + if (proxy == NULL) \ + return NULL; \ UNWRAP(v); \ + if (v == NULL) { \ + Py_XDECREF(proxy); \ + return NULL; \ + } \ if (w != NULL) { \ UNWRAP(w); \ + if (w == NULL) { \ + Py_XDECREF(proxy); \ + Py_XDECREF(v); \ + return NULL; \ + } \ } \ PyObject* res = generic(proxy, v, w); \ Py_DECREF(proxy); \ @@ -588,6 +603,8 @@ proxy_check_ref(PyObject *obj) static PyObject * \ method(PyObject *proxy, PyObject *Py_UNUSED(ignored)) { \ UNWRAP(proxy); \ + if (proxy == NULL) \ + return NULL; \ PyObject* res = PyObject_CallMethodNoArgs(proxy, &_Py_ID(SPECIAL)); \ Py_DECREF(proxy); \ return res; \ @@ -636,7 +653,13 @@ static PyObject * proxy_richcompare(PyObject *proxy, PyObject *v, int op) { UNWRAP(proxy); + if (proxy == NULL) + return NULL; UNWRAP(v); + if (v == NULL) { + Py_XDECREF(proxy); + return NULL; + } PyObject* res = PyObject_RichCompare(proxy, v, op); Py_DECREF(proxy); Py_DECREF(v); From 3836f9a4e7342bf6164a921f466b9e218843a899 Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Thu, 9 Apr 2026 23:54:09 +0530 Subject: [PATCH 02/11] add a cleanup using goto instead of returningh null --- Objects/weakrefobject.c | 135 ++++++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index 4b6a32bd102836..c7b12e8ba3e93e 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -530,24 +530,39 @@ proxy_check_ref(PyObject *obj) return true; } - -/* 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. +/* + * Unwrap a proxy into a strong reference. + * - If `o` is a live proxy: replaces `o` with the underlying object + * (already Py_INCREF'd by _PyWeakref_GET_REF), sets *did_incref = 1. + * - If `o` is a dead proxy: sets ReferenceError, sets `o` = NULL, + * sets *did_incref = 0. + * - If `o` is not a proxy: Py_INCREF's it, sets *did_incref = 1. + * Returns 1 on success, 0 on dead proxy (caller must goto error). */ -#define UNWRAP(o) \ - if (PyWeakref_CheckProxy(o)) { \ - o = _PyWeakref_GET_REF(o); \ - proxy_check_ref(o); \ - } \ - else { \ - Py_INCREF(o); \ +static inline int +_proxy_unwrap(PyObject **op, int *did_incref) +{ + if (PyWeakref_CheckProxy(*op)) { + *op = _PyWeakref_GET_REF(*op); + if (!proxy_check_ref(*op)) { + *did_incref = 0; + return 0; } + /* _PyWeakref_GET_REF already returned a strong ref */ + } + else { + Py_INCREF(*op); + } + *did_incref = 1; + return 1; +} #define WRAP_UNARY(method, generic) \ static PyObject * \ method(PyObject *proxy) { \ - UNWRAP(proxy); \ + int proxy_incref = 0; \ + if (!_proxy_unwrap(&proxy, &proxy_incref)) \ + return NULL; \ PyObject* res = generic(proxy); \ Py_DECREF(proxy); \ return res; \ @@ -556,18 +571,19 @@ proxy_check_ref(PyObject *obj) #define WRAP_BINARY(method, generic) \ static PyObject * \ method(PyObject *x, PyObject *y) { \ - UNWRAP(x); \ - if (x == NULL) \ - return NULL; \ - UNWRAP(y); \ - if (y == NULL) { \ - Py_XDECREF(x); \ - return NULL; \ + int x_incref = 0, y_incref = 0; \ + if (!_proxy_unwrap(&x, &x_incref)) goto clean_up; \ + if (!_proxy_unwrap(&y, &y_incref)) goto clean_up; \ + { \ + PyObject* res = generic(x, y); \ + Py_DECREF(x); \ + Py_DECREF(y); \ + return res; \ } \ - PyObject* res = generic(x, y); \ - Py_DECREF(x); \ - Py_DECREF(y); \ - return res; \ + clean_up: \ + if (x_incref) Py_DECREF(x); \ + if (y_incref) Py_DECREF(y); \ + return NULL; \ } /* Note that the third arg needs to be checked for NULL since the tp_call @@ -576,40 +592,36 @@ proxy_check_ref(PyObject *obj) #define WRAP_TERNARY(method, generic) \ static PyObject * \ method(PyObject *proxy, PyObject *v, PyObject *w) { \ - UNWRAP(proxy); \ - if (proxy == NULL) \ - return NULL; \ - UNWRAP(v); \ - if (v == NULL) { \ - Py_XDECREF(proxy); \ - return NULL; \ - } \ + int proxy_incref = 0, v_incref = 0, w_incref = 0; \ + if (!_proxy_unwrap(&proxy, &proxy_incref)) goto clean_up; \ + if (!_proxy_unwrap(&v, &v_incref)) goto clean_up; \ if (w != NULL) { \ - UNWRAP(w); \ - if (w == NULL) { \ - Py_XDECREF(proxy); \ - Py_XDECREF(v); \ - return NULL; \ - } \ + if (!_proxy_unwrap(&w, &w_incref)) goto clean_up; \ } \ - PyObject* res = generic(proxy, v, w); \ - Py_DECREF(proxy); \ - Py_DECREF(v); \ - Py_XDECREF(w); \ - return res; \ + { \ + PyObject* res = generic(proxy, v, w); \ + Py_DECREF(proxy); \ + Py_DECREF(v); \ + Py_XDECREF(w); \ + return res; \ + } \ + clean_up: \ + if (proxy_incref) Py_DECREF(proxy); \ + if (v_incref) Py_DECREF(v); \ + if (w_incref) Py_DECREF(w); \ + return NULL; \ } #define WRAP_METHOD(method, SPECIAL) \ static PyObject * \ method(PyObject *proxy, PyObject *Py_UNUSED(ignored)) { \ - UNWRAP(proxy); \ - if (proxy == NULL) \ - return NULL; \ - PyObject* res = PyObject_CallMethodNoArgs(proxy, &_Py_ID(SPECIAL)); \ - Py_DECREF(proxy); \ - return res; \ - } - + int proxy_incref = 0; \ + if (!_proxy_unwrap(&proxy, &proxy_incref)) \ + return NULL; \ + PyObject* res = PyObject_CallMethodNoArgs(proxy, &_Py_ID(SPECIAL)); \ + Py_DECREF(proxy); \ + return res; \ + } /* direct slots */ @@ -652,18 +664,19 @@ proxy_setattr(PyObject *proxy, PyObject *name, PyObject *value) static PyObject * proxy_richcompare(PyObject *proxy, PyObject *v, int op) { - UNWRAP(proxy); - if (proxy == NULL) - return NULL; - UNWRAP(v); - if (v == NULL) { - Py_XDECREF(proxy); - return NULL; + int proxy_incref = 0, v_incref = 0; + if (!_proxy_unwrap(&proxy, &proxy_incref)) goto clean_up; + if (!_proxy_unwrap(&v, &v_incref)) goto clean_up; + { + PyObject* res = PyObject_RichCompare(proxy, v, op); + Py_DECREF(proxy); + Py_DECREF(v); + return res; } - PyObject* res = PyObject_RichCompare(proxy, v, op); - Py_DECREF(proxy); - Py_DECREF(v); - return res; +clean_up: + if (proxy_incref) Py_DECREF(proxy); + if (v_incref) Py_DECREF(v); + return NULL; } /* number slots */ From 37b4577a5fd5a1c04ab64e41a6434067f6159ed3 Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Fri, 10 Apr 2026 14:44:33 +0530 Subject: [PATCH 03/11] fix lint error --- Lib/test/test_weakref.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 57790af43e274d..07d1971814ad34 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -542,52 +542,52 @@ def test_proxy_unref_add_refcount(self): class C: def __add__(self, o): return NotImplemented - + # create dead proxy o = C() dead = weakref.proxy(o) del o gc.collect() - + # create live proxy obj = C() ref = weakref.ref(obj) proxy = weakref.proxy(obj) - + try: operator.add(proxy, dead) except ReferenceError: pass - + del proxy, obj, dead gc.collect() - + self.assertIsNone(ref(), "Leaked object in add operation") - + def test_proxy_unref_pow_refcount(self): class C: def __pow__(self, o, m=None): return NotImplemented - + # create dead proxy o = C() dead = weakref.proxy(o) del o gc.collect() - + # create live proxy obj = C() ref = weakref.ref(obj) proxy = weakref.proxy(obj) - + try: pow(proxy, dead, None) except ReferenceError: pass - + del proxy, obj, dead gc.collect() - + self.assertIsNone(ref(), "Leaked object in pow operation") def test_getweakrefcount(self): From cf9b237b1e9db370f513a5f1b368bc8e69685dac Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Fri, 10 Apr 2026 16:19:43 +0530 Subject: [PATCH 04/11] add blurb --- .../2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst new file mode 100644 index 00000000000000..1927712506ee20 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst @@ -0,0 +1,2 @@ +fix refcount leak in weakref proxy unwrapping by introducing a safe +_proxy_unwrap helper From bcdaaac9e8328487e3c6b6558a08390cdf16a7ec Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Mon, 13 Apr 2026 11:57:44 +0530 Subject: [PATCH 05/11] organize testcases --- Lib/test/test_weakref.py | 66 ++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 07d1971814ad34..91953e4617b03b 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -538,57 +538,45 @@ class MyObj: with self.assertRaises(TypeError): hash(weakref.proxy(obj)) - def test_proxy_unref_add_refcount(self): - class C: - def __add__(self, o): - return NotImplemented - - # create dead proxy - o = C() + def _assert_no_proxy_refcount_leak(self, make_class, do_op, op_name): + """Helper: verify a proxy operation doesn't leak.""" + # Create dead proxy + o = make_class() dead = weakref.proxy(o) del o gc.collect() - - # create live proxy - obj = C() + + # Create live proxy + obj = make_class() ref = weakref.ref(obj) proxy = weakref.proxy(obj) - + + # run operation try: - operator.add(proxy, dead) + do_op(proxy, dead) except ReferenceError: pass - del proxy, obj, dead gc.collect() - - self.assertIsNone(ref(), "Leaked object in add operation") - - def test_proxy_unref_pow_refcount(self): + + # verify + self.assertIsNone(ref(), f"Leaked object in '{op_name}' operation") + + def test_proxy_unref_binary_refcount(self): class C: - def __pow__(self, o, m=None): - return NotImplemented - - # create dead proxy - o = C() - dead = weakref.proxy(o) - del o - gc.collect() - - # create live proxy - obj = C() - ref = weakref.ref(obj) - proxy = weakref.proxy(obj) - - try: - pow(proxy, dead, None) - except ReferenceError: - pass - - del proxy, obj, dead - gc.collect() + def __add__(self, o): return NotImplemented + self._assert_no_proxy_refcount_leak(C, operator.add, "Binary") + + def test_proxy_unref_ternary_refcount(self): + class C: + def __pow__(self, o, m=None): return NotImplemented + self._assert_no_proxy_refcount_leak(C, lambda p, d: pow(p, d, None), "Ternary") + + def test_proxy_unref_richcompare_refcount(self): + class C: + def __eq__(self, o): return NotImplemented + self._assert_no_proxy_refcount_leak(C, lambda p, d: p == d, "Rich Compare") - self.assertIsNone(ref(), "Leaked object in pow operation") def test_getweakrefcount(self): o = C() From 33808d9d9dde5e3c3f472eb8ce33fae0cd159ac3 Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Mon, 13 Apr 2026 11:57:50 +0530 Subject: [PATCH 06/11] Update test_weakref.py --- Lib/test/test_weakref.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 91953e4617b03b..6ca2cfa9f136a6 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -561,6 +561,11 @@ def _assert_no_proxy_refcount_leak(self, make_class, do_op, op_name): # verify self.assertIsNone(ref(), f"Leaked object in '{op_name}' operation") + + def test_proxy_unref_unary_refcount(self): + class C: + def __neg__(self): return 0 + self._assert_no_proxy_refcount_leak(C,lambda p, d: operator.neg(d), "Unary") def test_proxy_unref_binary_refcount(self): class C: @@ -577,6 +582,10 @@ class C: def __eq__(self, o): return NotImplemented self._assert_no_proxy_refcount_leak(C, lambda p, d: p == d, "Rich Compare") + def test_proxy_unref_wrapmethod_refcount(self): + class C: + def __repr__(self): return "C()" + self._assert_no_proxy_refcount_leak(C, lambda p, d: repr(d), "Wrap Method") def test_getweakrefcount(self): o = C() From 08ecaeaa3ca636424c3c6476186d25045a71fe2d Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Mon, 13 Apr 2026 12:01:48 +0530 Subject: [PATCH 07/11] Update 2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst --- .../2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst index 1927712506ee20..44de8343967a37 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst @@ -1,2 +1,10 @@ -fix refcount leak in weakref proxy unwrapping by introducing a safe -_proxy_unwrap helper +Fix refcount leak in weakref proxy operations: +When a weakref proxy was used in binary, ternary, or rich comparison operations +and the second argument turned out to be a dead proxy, the first argument's +reference count was already incremented by the unwrap step but never released, + causing a memory leak.This patch replaces the old UNWRAP macro with + a proper inline function _proxy_unwrap() that tracks whether each + argument was incref'd, and introduces cleanup paths (goto clean_up) + that correctly decref any already-acquired references before + returning NULL on failure. Also added reference cleanup in unary and compare + operations. \ No newline at end of file From c0f83ded7bbde690bf32cc1303bdce1d88b3d5cb Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Mon, 13 Apr 2026 12:05:53 +0530 Subject: [PATCH 08/11] fix lint --- Lib/test/test_weakref.py | 12 ++++++------ .../2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 6ca2cfa9f136a6..92b0dd07bb2a64 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -545,12 +545,12 @@ def _assert_no_proxy_refcount_leak(self, make_class, do_op, op_name): dead = weakref.proxy(o) del o gc.collect() - + # Create live proxy obj = make_class() ref = weakref.ref(obj) proxy = weakref.proxy(obj) - + # run operation try: do_op(proxy, dead) @@ -558,7 +558,7 @@ def _assert_no_proxy_refcount_leak(self, make_class, do_op, op_name): pass del proxy, obj, dead gc.collect() - + # verify self.assertIsNone(ref(), f"Leaked object in '{op_name}' operation") @@ -566,17 +566,17 @@ def test_proxy_unref_unary_refcount(self): class C: def __neg__(self): return 0 self._assert_no_proxy_refcount_leak(C,lambda p, d: operator.neg(d), "Unary") - + def test_proxy_unref_binary_refcount(self): class C: def __add__(self, o): return NotImplemented self._assert_no_proxy_refcount_leak(C, operator.add, "Binary") - + def test_proxy_unref_ternary_refcount(self): class C: def __pow__(self, o, m=None): return NotImplemented self._assert_no_proxy_refcount_leak(C, lambda p, d: pow(p, d, None), "Ternary") - + def test_proxy_unref_richcompare_refcount(self): class C: def __eq__(self, o): return NotImplemented diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst index 44de8343967a37..71320f230d4305 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst @@ -6,5 +6,5 @@ reference count was already incremented by the unwrap step but never released, a proper inline function _proxy_unwrap() that tracks whether each argument was incref'd, and introduces cleanup paths (goto clean_up) that correctly decref any already-acquired references before - returning NULL on failure. Also added reference cleanup in unary and compare + returning NULL on failure. Also added reference cleanup in unary and compare operations. \ No newline at end of file From eb3cfe6c5b5d21b3f29bab58ae0dd748bacbf265 Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Mon, 13 Apr 2026 12:09:56 +0530 Subject: [PATCH 09/11] fix lint issues --- .../2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst index 71320f230d4305..733585a55359f7 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst @@ -7,4 +7,5 @@ reference count was already incremented by the unwrap step but never released, argument was incref'd, and introduces cleanup paths (goto clean_up) that correctly decref any already-acquired references before returning NULL on failure. Also added reference cleanup in unary and compare - operations. \ No newline at end of file + operations. + \ No newline at end of file From 39cbf1f882dcc2bdfb1735a1eef505eb8b6d3f92 Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Mon, 13 Apr 2026 12:10:01 +0530 Subject: [PATCH 10/11] Update 2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst --- ...-04-10-16-19-23.gh-issue-148263.trqp3-.rst | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst index 733585a55359f7..4865ec3fee1eeb 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst @@ -1,11 +1,8 @@ -Fix refcount leak in weakref proxy operations: -When a weakref proxy was used in binary, ternary, or rich comparison operations -and the second argument turned out to be a dead proxy, the first argument's -reference count was already incremented by the unwrap step but never released, - causing a memory leak.This patch replaces the old UNWRAP macro with - a proper inline function _proxy_unwrap() that tracks whether each - argument was incref'd, and introduces cleanup paths (goto clean_up) - that correctly decref any already-acquired references before - returning NULL on failure. Also added reference cleanup in unary and compare - operations. - \ No newline at end of file +Fix refcount leak in weakref proxy operations: When a weakref proxy was used in binary, +ternary, or rich comparison operations and the second argument turned out to be a dead +proxy, the first argument's reference count was already incremented by the unwrap step +but never released, causing a memory leak.This patch replaces the old UNWRAP macro with +a proper inline function _proxy_unwrap() that tracks whether each argument was incref'd, +and introduces cleanup paths (goto clean_up) that correctly decref any already-acquired +references before returning NULL on failure. Also added reference cleanup in unary and +compare operations. From c9efd8979adc27867cbc812b5816107129edf5dd Mon Sep 17 00:00:00 2001 From: Prakash Sellathurai Date: Mon, 13 Apr 2026 21:23:10 +0530 Subject: [PATCH 11/11] Update 2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst --- .../2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst index 4865ec3fee1eeb..9d7e407d59fb44 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-16-19-23.gh-issue-148263.trqp3-.rst @@ -1,8 +1 @@ -Fix refcount leak in weakref proxy operations: When a weakref proxy was used in binary, -ternary, or rich comparison operations and the second argument turned out to be a dead -proxy, the first argument's reference count was already incremented by the unwrap step -but never released, causing a memory leak.This patch replaces the old UNWRAP macro with -a proper inline function _proxy_unwrap() that tracks whether each argument was incref'd, -and introduces cleanup paths (goto clean_up) that correctly decref any already-acquired -references before returning NULL on failure. Also added reference cleanup in unary and -compare operations. +Fix reference count leak in weakref proxy operations when a dead proxy is encountered as the second argument.