1
1
import unittest
2
+ import unittest .mock
2
3
from test .support import (verbose , refcount_test , run_unittest ,
3
4
strip_python_stderr , cpython_only , start_threads ,
4
5
temp_dir , requires_type_collecting , TESTFN , unlink ,
@@ -22,6 +23,11 @@ def __new__(cls, *args, **kwargs):
22
23
raise TypeError ('requires _testcapi.with_tp_del' )
23
24
return C
24
25
26
+ try :
27
+ from _testcapi import ContainerNoGC
28
+ except ImportError :
29
+ ContainerNoGC = None
30
+
25
31
### Support code
26
32
###############################################################################
27
33
@@ -959,6 +965,66 @@ def getstats():
959
965
960
966
gc .enable ()
961
967
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
+
962
1028
class GCCallbackTests (unittest .TestCase ):
963
1029
def setUp (self ):
964
1030
# Save gc state and disable it.
0 commit comments