11import unittest
2+ import unittest .mock
23from test .support import (verbose , refcount_test , run_unittest ,
34 strip_python_stderr , cpython_only , start_threads ,
45 temp_dir , requires_type_collecting , TESTFN , unlink ,
@@ -22,6 +23,11 @@ def __new__(cls, *args, **kwargs):
2223 raise TypeError ('requires _testcapi.with_tp_del' )
2324 return C
2425
26+ try :
27+ from _testcapi import ContainerNoGC
28+ except ImportError :
29+ ContainerNoGC = None
30+
2531### Support code
2632###############################################################################
2733
@@ -959,6 +965,66 @@ def getstats():
959965
960966 gc .enable ()
961967
968+ @unittest .skipIf (ContainerNoGC is None ,
969+ 'requires ContainerNoGC extension type' )
970+ def test_trash_weakref_clear (self ):
971+ # Test that trash weakrefs are properly cleared (bpo-38006).
972+ #
973+ # Structure we are creating:
974+ #
975+ # Z <- Y <- A--+--> WZ -> C
976+ # ^ |
977+ # +--+
978+ # where:
979+ # WZ is a weakref to Z with callback C
980+ # Y doesn't implement tp_traverse
981+ # A contains a reference to itself, Y and WZ
982+ #
983+ # A, Y, Z, WZ are all trash. The GC doesn't know that Z is trash
984+ # because Y does not implement tp_traverse. To show the bug, WZ needs
985+ # to live long enough so that Z is deallocated before it. Then, if
986+ # gcmodule is buggy, when Z is being deallocated, C will run.
987+ #
988+ # To ensure WZ lives long enough, we put it in a second reference
989+ # cycle. That trick only works due to the ordering of the GC prev/next
990+ # linked lists. So, this test is a bit fragile.
991+ #
992+ # The bug reported in bpo-38006 is caused because the GC did not
993+ # clear WZ before starting the process of calling tp_clear on the
994+ # trash. Normally, handle_weakrefs() would find the weakref via Z and
995+ # clear it. However, since the GC cannot find Z, WR is not cleared and
996+ # it can execute during delete_garbage(). That can lead to disaster
997+ # since the callback might tinker with objects that have already had
998+ # tp_clear called on them (leaving them in possibly invalid states).
999+
1000+ callback = unittest .mock .Mock ()
1001+
1002+ class A :
1003+ __slots__ = ['a' , 'y' , 'wz' ]
1004+
1005+ class Z :
1006+ pass
1007+
1008+ # setup required object graph, as described above
1009+ a = A ()
1010+ a .a = a
1011+ a .y = ContainerNoGC (Z ())
1012+ a .wz = weakref .ref (a .y .value , callback )
1013+ # create second cycle to keep WZ alive longer
1014+ wr_cycle = [a .wz ]
1015+ wr_cycle .append (wr_cycle )
1016+ # ensure trash unrelated to this test is gone
1017+ gc .collect ()
1018+ gc .disable ()
1019+ # release references and create trash
1020+ del a , wr_cycle
1021+ gc .collect ()
1022+ # if called, it means there is a bug in the GC. The weakref should be
1023+ # cleared before Z dies.
1024+ callback .assert_not_called ()
1025+ gc .enable ()
1026+
1027+
9621028class GCCallbackTests (unittest .TestCase ):
9631029 def setUp (self ):
9641030 # Save gc state and disable it.
0 commit comments