import gc import _testcapi import os import ctypes lib = ctypes.pythonapi PyObject_GC_Track = lib.PyObject_GC_Track PyObject_GC_Track.argtypes = (ctypes.py_object,) PyObject_GC_Track.restype = None PyObject_GC_UnTrack = lib.PyObject_GC_UnTrack PyObject_GC_UnTrack.argtypes = (ctypes.py_object,) PyObject_GC_UnTrack.restype = None def PyGC_retrack(obj): PyObject_GC_UnTrack(obj) PyObject_GC_Track(obj) class Namespace: pass def func(): # Strong references: # remove <-> ns # and # ns -> obj -> remove ns = Namespace() obj = _testcapi.BadGC2Type() def remove(arg): # create a closure to keep obj alive x = ns return "must never be called" remove.__name__ = "evil_func" assert remove.__closure__[0].cell_contents is ns obj.set_func(remove) ns.obj = obj ns.remove = remove # reorder objects in GC collection generation 0 PyGC_retrack(remove) PyGC_retrack(remove.__closure__[0]) PyGC_retrack(ns) PyGC_retrack(ns.__dict__) PyGC_retrack(obj) if 0: # dump some info for obj in gc.get_objects(0)[-5:]: print(type(obj), repr(obj)[:60]) print() print("obj", id(obj)) print("remove", id(remove)) func() gc.collect()