summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorDennis Sweeney <36520290+sweeneyde@users.noreply.github.com>2022-04-21 06:06:35 (GMT)
committerGitHub <noreply@github.com>2022-04-21 06:06:35 (GMT)
commitf2b4e458b3327130e46edb4efe8e1847de09efc5 (patch)
treedd53bed89cdbccf19083f11038516f20c6aa4036 /Lib
parent615b24c80b0bbbc7b70aa1e9c28f9c4463c8c1ed (diff)
downloadcpython-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.py67
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():