From 06145a6ed1691a82f33fdaad33816a01cb04c2b8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 9 Jun 2026 14:51:04 +0300 Subject: [PATCH 1/2] gh-80384: Check that callback is callable at weak reference creation * Python functions weakref.ref() and weakref.proxy() now raise TypeError if the callback argument is not callable or None. * C functions PyWeakref_NewRef() and PyWeakref_NewProxy() now raise TypeError if the callback argument is not callable, None, or NULL. --- Doc/c-api/weakref.rst | 12 ++++++++++-- Doc/library/weakref.rst | 6 ++++++ Lib/test/test_capi/test_weakref.py | 2 ++ Lib/test/test_weakref.py | 5 +++++ .../2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst | 3 +++ .../2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst | 2 ++ Objects/weakrefobject.c | 9 ++++++++- 7 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst create mode 100644 Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst diff --git a/Doc/c-api/weakref.rst b/Doc/c-api/weakref.rst index db6ae0a9d4ea3d..8762a003c5218d 100644 --- a/Doc/c-api/weakref.rst +++ b/Doc/c-api/weakref.rst @@ -43,7 +43,11 @@ as much as it can. should accept a single parameter, which will be the weak reference object itself. *callback* may also be ``None`` or ``NULL``. If *ob* is not a weakly referenceable object, or if *callback* is not callable, ``None``, or - ``NULL``, this will return ``NULL`` and raise :exc:`TypeError`. + ``NULL``, this will raise :exc:`TypeError` and return ``NULL``. + + .. versionchanged:: next + Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or + ``NULL``. .. seealso:: :c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly @@ -59,7 +63,11 @@ as much as it can. collected; it should accept a single parameter, which will be the weak reference object itself. *callback* may also be ``None`` or ``NULL``. If *ob* is not a weakly referenceable object, or if *callback* is not callable, - ``None``, or ``NULL``, this will return ``NULL`` and raise :exc:`TypeError`. + ``NULL``, this will raise :exc:`TypeError` and return ``NULL``. + + .. versionchanged:: next + Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or + ``NULL``. .. seealso:: :c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly diff --git a/Doc/library/weakref.rst b/Doc/library/weakref.rst index fcb9e0199fad69..952609c0e700e5 100644 --- a/Doc/library/weakref.rst +++ b/Doc/library/weakref.rst @@ -132,6 +132,9 @@ See :ref:`__slots__ documentation ` for details. .. versionchanged:: 3.4 Added the :attr:`__callback__` attribute. + .. versionchanged:: next + Raise :exc:`!TypeError` if *callback* is not callable or ``None``. + .. function:: proxy(object[, callback]) @@ -151,6 +154,9 @@ See :ref:`__slots__ documentation ` for details. Extended the operator support on proxy objects to include the matrix multiplication operators ``@`` and ``@=``. + .. versionchanged:: next + Raise :exc:`!TypeError` if *callback* is not callable or ``None``. + .. function:: getweakrefcount(object) diff --git a/Lib/test/test_capi/test_weakref.py b/Lib/test/test_capi/test_weakref.py index 86ebe92da8d95d..7c9e97f571979d 100644 --- a/Lib/test/test_capi/test_weakref.py +++ b/Lib/test/test_capi/test_weakref.py @@ -97,6 +97,7 @@ def test_pyweakref_newref(self): # PyWeakref_NewRef() handles None callback as NULL callback wr = newref(obj, None) self.assertIs(type(wr), weakref.ReferenceType) + self.assertRaises(TypeError, newref, obj, 42) log = [] wr = newref(obj, log.append) self.assertIs(type(wr), weakref.ReferenceType) @@ -116,6 +117,7 @@ def test_pyweakref_newproxy(self): # PyWeakref_NewProxy() handles None callback as NULL callback wp = newproxy(obj, None) self.assertIs(type(wp), weakref.ProxyType) + self.assertRaises(TypeError, newproxy, obj, 42) log = [] wp = newproxy(obj, log.append) self.assertIs(type(wp), weakref.ProxyType) diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index b187643e84521c..0c4717521f16f6 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -167,6 +167,11 @@ def test_basic_callback(self): self.check_basic_callback(create_function) self.check_basic_callback(create_bound_method) + def test_non_callable_callback(self): + c = C() + self.assertRaises(TypeError, weakref.ref, c, 42) + self.assertRaises(TypeError, weakref.proxy, c, 42) + @support.cpython_only def test_cfunction(self): _testcapi = import_helper.import_module("_testcapi") diff --git a/Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst b/Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst new file mode 100644 index 00000000000000..c4f5e8f3f9bbc2 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst @@ -0,0 +1,3 @@ +:c:func:`PyWeakref_NewRef` and :c:func:`PyWeakref_NewProxy` now raise +:exc:`TypeError` if the *callback* argument is not callable, ``None``, or +``NULL``. diff --git a/Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst b/Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst new file mode 100644 index 00000000000000..53d4d513ba757d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst @@ -0,0 +1,2 @@ +:func:`weakref.ref` and :func:`weakref.proxy` now raise :exc:`TypeError` if +the *callback* argument is not callable or ``None``. diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index 8446a2dbcf7559..daeba5368b2166 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -416,8 +416,15 @@ get_or_create_weakref(PyTypeObject *type, PyObject *obj, PyObject *callback) Py_TYPE(obj)->tp_name); return NULL; } - if (callback == Py_None) + if (callback == Py_None) { callback = NULL; + } + if (callback != NULL && !PyCallable_Check(callback)) { + PyErr_Format(PyExc_TypeError, + "callback must be callable or None, not '%T'", + callback); + return NULL; + } PyWeakReference **list = GET_WEAKREFS_LISTPTR(obj); if ((type == &_PyWeakref_RefType) || From 88afc02b32ce1e5d7fecea5f7b3efdc356f06e4d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 9 Jun 2026 15:56:00 +0300 Subject: [PATCH 2/2] Fix test_recursion_error_during_traceback in test_traceback. --- Lib/test/test_traceback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 7dc3364561d8a1..2c6324a14a8e2f 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -206,7 +206,7 @@ def test_recursion_error_during_traceback(self): sys.setrecursionlimit(15) def f(): - ref(lambda: 0, []) + ref(lambda: 0, ord) f() try: