diff options
author | Dennis Sweeney <36520290+sweeneyde@users.noreply.github.com> | 2022-04-21 06:06:35 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-21 06:06:35 (GMT) |
commit | f2b4e458b3327130e46edb4efe8e1847de09efc5 (patch) | |
tree | dd53bed89cdbccf19083f11038516f20c6aa4036 /Lib | |
parent | 615b24c80b0bbbc7b70aa1e9c28f9c4463c8c1ed (diff) | |
download | cpython-f2b4e458b3327130e46edb4efe8e1847de09efc5.zip cpython-f2b4e458b3327130e46edb4efe8e1847de09efc5.tar.gz cpython-f2b4e458b3327130e46edb4efe8e1847de09efc5.tar.bz2 |
gh-91636: Don't clear required fields of function objects (GH-91651)
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_gc.py | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index ce04042..dbbd67b 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -227,6 +227,73 @@ class GCTests(unittest.TestCase): 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(): |