Skip to content

Commit f2b4e45

Browse files
authored
gh-91636: Don't clear required fields of function objects (GH-91651)
1 parent 615b24c commit f2b4e45

File tree

3 files changed

+79
-3
lines changed

3 files changed

+79
-3
lines changed

Lib/test/test_gc.py

+67
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,73 @@ def test_function(self):
227227
del d
228228
self.assertEqual(gc.collect(), 2)
229229

230+
def test_function_tp_clear_leaves_consistent_state(self):
231+
# https://github.com/python/cpython/issues/91636
232+
code = """if 1:
233+
234+
import gc
235+
import weakref
236+
237+
class LateFin:
238+
__slots__ = ('ref',)
239+
240+
def __del__(self):
241+
242+
# 8. Now `latefin`'s finalizer is called. Here we
243+
# obtain a reference to `func`, which is currently
244+
# undergoing `tp_clear`.
245+
global func
246+
func = self.ref()
247+
248+
class Cyclic(tuple):
249+
__slots__ = ()
250+
251+
# 4. The finalizers of all garbage objects are called. In
252+
# this case this is only us as `func` doesn't have a
253+
# finalizer.
254+
def __del__(self):
255+
256+
# 5. Create a weakref to `func` now. If we had created
257+
# it earlier, it would have been cleared by the
258+
# garbage collector before calling the finalizers.
259+
self[1].ref = weakref.ref(self[0])
260+
261+
# 6. Drop the global reference to `latefin`. The only
262+
# remaining reference is the one we have.
263+
global latefin
264+
del latefin
265+
266+
# 7. Now `func` is `tp_clear`-ed. This drops the last
267+
# reference to `Cyclic`, which gets `tp_dealloc`-ed.
268+
# This drops the last reference to `latefin`.
269+
270+
latefin = LateFin()
271+
def func():
272+
pass
273+
cyc = tuple.__new__(Cyclic, (func, latefin))
274+
275+
# 1. Create a reference cycle of `cyc` and `func`.
276+
func.__module__ = cyc
277+
278+
# 2. Make the cycle unreachable, but keep the global reference
279+
# to `latefin` so that it isn't detected as garbage. This
280+
# way its finalizer will not be called immediately.
281+
del func, cyc
282+
283+
# 3. Invoke garbage collection,
284+
# which will find `cyc` and `func` as garbage.
285+
gc.collect()
286+
287+
# 9. Previously, this would crash because `func_qualname`
288+
# had been NULL-ed out by func_clear().
289+
print(f"{func=}")
290+
"""
291+
# We're mostly just checking that this doesn't crash.
292+
rc, stdout, stderr = assert_python_ok("-c", code)
293+
self.assertEqual(rc, 0)
294+
self.assertRegex(stdout, rb"""\A\s*func=<function at \S+>\s*\Z""")
295+
self.assertFalse(stderr)
296+
230297
@refcount_test
231298
def test_frame(self):
232299
def f():
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed a crash in a garbage-collection edge-case, in which a ``PyFunction_Type.tp_clear`` function could leave a python function object in an inconsistent state.

Objects/funcobject.c

+11-3
Original file line numberDiff line numberDiff line change
@@ -678,18 +678,22 @@ static int
678678
func_clear(PyFunctionObject *op)
679679
{
680680
op->func_version = 0;
681-
Py_CLEAR(op->func_code);
682681
Py_CLEAR(op->func_globals);
683682
Py_CLEAR(op->func_builtins);
684-
Py_CLEAR(op->func_name);
685-
Py_CLEAR(op->func_qualname);
686683
Py_CLEAR(op->func_module);
687684
Py_CLEAR(op->func_defaults);
688685
Py_CLEAR(op->func_kwdefaults);
689686
Py_CLEAR(op->func_doc);
690687
Py_CLEAR(op->func_dict);
691688
Py_CLEAR(op->func_closure);
692689
Py_CLEAR(op->func_annotations);
690+
// Don't Py_CLEAR(op->func_code), since code is always required
691+
// to be non-NULL. Similarly, name and qualname shouldn't be NULL.
692+
// However, name and qualname could be str subclasses, so they
693+
// could have reference cycles. The solution is to replace them
694+
// with a genuinely immutable string.
695+
Py_SETREF(op->func_name, Py_NewRef(&_Py_STR(empty)));
696+
Py_SETREF(op->func_qualname, Py_NewRef(&_Py_STR(empty)));
693697
return 0;
694698
}
695699

@@ -701,6 +705,10 @@ func_dealloc(PyFunctionObject *op)
701705
PyObject_ClearWeakRefs((PyObject *) op);
702706
}
703707
(void)func_clear(op);
708+
// These aren't cleared by func_clear().
709+
Py_DECREF(op->func_code);
710+
Py_DECREF(op->func_name);
711+
Py_DECREF(op->func_qualname);
704712
PyObject_GC_Del(op);
705713
}
706714

0 commit comments

Comments
 (0)