| 1064 | @unittest.skipIf(ContainerNoGC is None, |
| 1065 | 'requires ContainerNoGC extension type') |
| 1066 | def test_trash_weakref_clear(self): |
| 1067 | # Test that trash weakrefs are properly cleared (bpo-38006). |
| 1068 | # |
| 1069 | # Structure we are creating: |
| 1070 | # |
| 1071 | # Z <- Y <- A--+--> WZ -> C |
| 1072 | # ^ | |
| 1073 | # +--+ |
| 1074 | # where: |
| 1075 | # WZ is a weakref to Z with callback C |
| 1076 | # Y doesn't implement tp_traverse |
| 1077 | # A contains a reference to itself, Y and WZ |
| 1078 | # |
| 1079 | # A, Y, Z, WZ are all trash. The GC doesn't know that Z is trash |
| 1080 | # because Y does not implement tp_traverse. To show the bug, WZ needs |
| 1081 | # to live long enough so that Z is deallocated before it. Then, if |
| 1082 | # gcmodule is buggy, when Z is being deallocated, C will run. |
| 1083 | # |
| 1084 | # To ensure WZ lives long enough, we put it in a second reference |
| 1085 | # cycle. That trick only works due to the ordering of the GC prev/next |
| 1086 | # linked lists. So, this test is a bit fragile. |
| 1087 | # |
| 1088 | # The bug reported in bpo-38006 is caused because the GC did not |
| 1089 | # clear WZ before starting the process of calling tp_clear on the |
| 1090 | # trash. Normally, handle_weakrefs() would find the weakref via Z and |
| 1091 | # clear it. However, since the GC cannot find Z, WR is not cleared and |
| 1092 | # it can execute during delete_garbage(). That can lead to disaster |
| 1093 | # since the callback might tinker with objects that have already had |
| 1094 | # tp_clear called on them (leaving them in possibly invalid states). |
| 1095 | |
| 1096 | callback = unittest.mock.Mock() |
| 1097 | |
| 1098 | class A: |
| 1099 | __slots__ = ['a', 'y', 'wz'] |
| 1100 | |
| 1101 | class Z: |
| 1102 | pass |
| 1103 | |
| 1104 | # setup required object graph, as described above |
| 1105 | a = A() |
| 1106 | a.a = a |
| 1107 | a.y = ContainerNoGC(Z()) |
| 1108 | a.wz = weakref.ref(a.y.value, callback) |
| 1109 | # create second cycle to keep WZ alive longer |
| 1110 | wr_cycle = [a.wz] |
| 1111 | wr_cycle.append(wr_cycle) |
| 1112 | # ensure trash unrelated to this test is gone |
| 1113 | gc.collect() |
| 1114 | gc.disable() |
| 1115 | # release references and create trash |
| 1116 | del a, wr_cycle |
| 1117 | gc.collect() |
| 1118 | # if called, it means there is a bug in the GC. The weakref should be |
| 1119 | # cleared before Z dies. |
| 1120 | callback.assert_not_called() |
| 1121 | gc.enable() |
| 1122 | |
| 1123 | @cpython_only |