@@ -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 ():
0 commit comments