Skip to content

Commit 06145a6

Browse files
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.
1 parent c3cd75a commit 06145a6

7 files changed

Lines changed: 36 additions & 3 deletions

File tree

Doc/c-api/weakref.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ as much as it can.
4343
should accept a single parameter, which will be the weak reference object
4444
itself. *callback* may also be ``None`` or ``NULL``. If *ob* is not a
4545
weakly referenceable object, or if *callback* is not callable, ``None``, or
46-
``NULL``, this will return ``NULL`` and raise :exc:`TypeError`.
46+
``NULL``, this will raise :exc:`TypeError` and return ``NULL``.
47+
48+
.. versionchanged:: next
49+
Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or
50+
``NULL``.
4751
4852
.. seealso::
4953
:c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly
@@ -59,7 +63,11 @@ as much as it can.
5963
collected; it should accept a single parameter, which will be the weak
6064
reference object itself. *callback* may also be ``None`` or ``NULL``. If *ob*
6165
is not a weakly referenceable object, or if *callback* is not callable,
62-
``None``, or ``NULL``, this will return ``NULL`` and raise :exc:`TypeError`.
66+
``NULL``, this will raise :exc:`TypeError` and return ``NULL``.
67+
68+
.. versionchanged:: next
69+
Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or
70+
``NULL``.
6371
6472
.. seealso::
6573
:c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly

Doc/library/weakref.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ See :ref:`__slots__ documentation <slots>` for details.
132132
.. versionchanged:: 3.4
133133
Added the :attr:`__callback__` attribute.
134134

135+
.. versionchanged:: next
136+
Raise :exc:`!TypeError` if *callback* is not callable or ``None``.
137+
135138

136139
.. function:: proxy(object[, callback])
137140

@@ -151,6 +154,9 @@ See :ref:`__slots__ documentation <slots>` for details.
151154
Extended the operator support on proxy objects to include the matrix
152155
multiplication operators ``@`` and ``@=``.
153156

157+
.. versionchanged:: next
158+
Raise :exc:`!TypeError` if *callback* is not callable or ``None``.
159+
154160

155161
.. function:: getweakrefcount(object)
156162

Lib/test/test_capi/test_weakref.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ def test_pyweakref_newref(self):
9797
# PyWeakref_NewRef() handles None callback as NULL callback
9898
wr = newref(obj, None)
9999
self.assertIs(type(wr), weakref.ReferenceType)
100+
self.assertRaises(TypeError, newref, obj, 42)
100101
log = []
101102
wr = newref(obj, log.append)
102103
self.assertIs(type(wr), weakref.ReferenceType)
@@ -116,6 +117,7 @@ def test_pyweakref_newproxy(self):
116117
# PyWeakref_NewProxy() handles None callback as NULL callback
117118
wp = newproxy(obj, None)
118119
self.assertIs(type(wp), weakref.ProxyType)
120+
self.assertRaises(TypeError, newproxy, obj, 42)
119121
log = []
120122
wp = newproxy(obj, log.append)
121123
self.assertIs(type(wp), weakref.ProxyType)

Lib/test/test_weakref.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ def test_basic_callback(self):
167167
self.check_basic_callback(create_function)
168168
self.check_basic_callback(create_bound_method)
169169

170+
def test_non_callable_callback(self):
171+
c = C()
172+
self.assertRaises(TypeError, weakref.ref, c, 42)
173+
self.assertRaises(TypeError, weakref.proxy, c, 42)
174+
170175
@support.cpython_only
171176
def test_cfunction(self):
172177
_testcapi = import_helper.import_module("_testcapi")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:c:func:`PyWeakref_NewRef` and :c:func:`PyWeakref_NewProxy` now raise
2+
:exc:`TypeError` if the *callback* argument is not callable, ``None``, or
3+
``NULL``.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`weakref.ref` and :func:`weakref.proxy` now raise :exc:`TypeError` if
2+
the *callback* argument is not callable or ``None``.

Objects/weakrefobject.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,15 @@ get_or_create_weakref(PyTypeObject *type, PyObject *obj, PyObject *callback)
416416
Py_TYPE(obj)->tp_name);
417417
return NULL;
418418
}
419-
if (callback == Py_None)
419+
if (callback == Py_None) {
420420
callback = NULL;
421+
}
422+
if (callback != NULL && !PyCallable_Check(callback)) {
423+
PyErr_Format(PyExc_TypeError,
424+
"callback must be callable or None, not '%T'",
425+
callback);
426+
return NULL;
427+
}
421428

422429
PyWeakReference **list = GET_WEAKREFS_LISTPTR(obj);
423430
if ((type == &_PyWeakref_RefType) ||

0 commit comments

Comments
 (0)