del d
self.assertEqual(gc.collect(), 2)
+ def test_function_tp_clear_leaves_consistent_state(self):
+ # https://github.com/python/cpython/issues/91636
+ code = """if 1:
+
+ import gc
+ import weakref
+
+ class LateFin:
+ __slots__ = ('ref',)
+
+ def __del__(self):
+
+ # 8. Now `latefin`'s finalizer is called. Here we
+ # obtain a reference to `func`, which is currently
+ # undergoing `tp_clear`.
+ global func
+ func = self.ref()
+
+ class Cyclic(tuple):
+ __slots__ = ()
+
+ # 4. The finalizers of all garbage objects are called. In
+ # this case this is only us as `func` doesn't have a
+ # finalizer.
+ def __del__(self):
+
+ # 5. Create a weakref to `func` now. If we had created
+ # it earlier, it would have been cleared by the
+ # garbage collector before calling the finalizers.
+ self[1].ref = weakref.ref(self[0])
+
+ # 6. Drop the global reference to `latefin`. The only
+ # remaining reference is the one we have.
+ global latefin
+ del latefin
+
+ # 7. Now `func` is `tp_clear`-ed. This drops the last
+ # reference to `Cyclic`, which gets `tp_dealloc`-ed.
+ # This drops the last reference to `latefin`.
+
+ latefin = LateFin()
+ def func():
+ pass
+ cyc = tuple.__new__(Cyclic, (func, latefin))
+
+ # 1. Create a reference cycle of `cyc` and `func`.
+ func.__module__ = cyc
+
+ # 2. Make the cycle unreachable, but keep the global reference
+ # to `latefin` so that it isn't detected as garbage. This
+ # way its finalizer will not be called immediately.
+ del func, cyc
+
+ # 3. Invoke garbage collection,
+ # which will find `cyc` and `func` as garbage.
+ gc.collect()
+
+ # 9. Previously, this would crash because `func_qualname`
+ # had been NULL-ed out by func_clear().
+ print(f"{func=}")
+ """
+ # We're mostly just checking that this doesn't crash.
+ rc, stdout, stderr = assert_python_ok("-c", code)
+ self.assertEqual(rc, 0)
+ self.assertRegex(stdout, rb"""\A\s*func=<function at \S+>\s*\Z""")
+ self.assertFalse(stderr)
+
@refcount_test
def test_frame(self):
def f():
func_clear(PyFunctionObject *op)
{
op->func_version = 0;
- Py_CLEAR(op->func_code);
Py_CLEAR(op->func_globals);
Py_CLEAR(op->func_builtins);
- Py_CLEAR(op->func_name);
- Py_CLEAR(op->func_qualname);
Py_CLEAR(op->func_module);
Py_CLEAR(op->func_defaults);
Py_CLEAR(op->func_kwdefaults);
Py_CLEAR(op->func_dict);
Py_CLEAR(op->func_closure);
Py_CLEAR(op->func_annotations);
+ // Don't Py_CLEAR(op->func_code), since code is always required
+ // to be non-NULL. Similarly, name and qualname shouldn't be NULL.
+ // However, name and qualname could be str subclasses, so they
+ // could have reference cycles. The solution is to replace them
+ // with a genuinely immutable string.
+ Py_SETREF(op->func_name, Py_NewRef(&_Py_STR(empty)));
+ Py_SETREF(op->func_qualname, Py_NewRef(&_Py_STR(empty)));
return 0;
}
PyObject_ClearWeakRefs((PyObject *) op);
}
(void)func_clear(op);
+ // These aren't cleared by func_clear().
+ Py_DECREF(op->func_code);
+ Py_DECREF(op->func_name);
+ Py_DECREF(op->func_qualname);
PyObject_GC_Del(op);
}