]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-80384: Check that callback is callable at weak reference creation (GH-151145)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 10 Jun 2026 10:34:55 +0000 (13:34 +0300)
committerGitHub <noreply@github.com>
Wed, 10 Jun 2026 10:34:55 +0000 (13:34 +0300)
* 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.

Co-authored-by: Maxwell Bernstein <emacs@fb.com>
Doc/c-api/weakref.rst
Doc/library/weakref.rst
Lib/test/test_capi/test_weakref.py
Lib/test/test_traceback.py
Lib/test/test_weakref.py
Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst [new file with mode: 0644]
Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst [new file with mode: 0644]
Objects/weakrefobject.c

index db6ae0a9d4ea3d7f967cc772e7fd1875a2c3b024..8762a003c5218d3267ed9430ce7ea0a1e7c9cce6 100644 (file)
@@ -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
index fcb9e0199fad69e966045abcb5698a626cdd0f12..952609c0e700e5e4f5233e478c7bb912ccede3da 100644 (file)
@@ -132,6 +132,9 @@ See :ref:`__slots__ documentation <slots>` 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 <slots>` 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)
 
index 86ebe92da8d95db58444f4755561055b4456d876..7c9e97f571979dccb3744bea75801bbd33e02456 100644 (file)
@@ -97,6 +97,7 @@ class CAPIWeakrefTest(unittest.TestCase):
         # 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 @@ class CAPIWeakrefTest(unittest.TestCase):
         # 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)
index 7dc3364561d8a115bcb57cec53b6c8716dd1aa2b..2c6324a14a8e2fccebed2e855192c94991047b1c 100644 (file)
@@ -206,7 +206,7 @@ class TracebackCases(unittest.TestCase):
                 sys.setrecursionlimit(15)
 
                 def f():
-                    ref(lambda: 0, [])
+                    ref(lambda: 0, ord)
                     f()
 
                 try:
index b187643e84521cc8e6d8c790a958dcb273670eb6..0c4717521f16f67bc5401a5af9368c82473c8c27 100644 (file)
@@ -167,6 +167,11 @@ class ReferencesTestCase(TestBase):
         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 (file)
index 0000000..c4f5e8f
--- /dev/null
@@ -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 (file)
index 0000000..53d4d51
--- /dev/null
@@ -0,0 +1,2 @@
+:func:`weakref.ref` and :func:`weakref.proxy` now raise :exc:`TypeError` if
+the *callback* argument is not callable or ``None``.
index 8446a2dbcf75593a36ae38513d05789a27f787c5..daeba5368b21660444510109d120e890b09a213d 100644 (file)
@@ -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) ||