@@ -227,6 +227,73 @@ def test_function(self):
227
227
del d
228
228
self .assertEqual (gc .collect (), 2 )
229
229
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
+
230
297
@refcount_test
231
298
def test_frame (self ):
232
299
def f ():
0 commit comments