diff --git a/Include/cpython/objimpl.h b/Include/cpython/objimpl.h index f121922bc42ced..7d60bbab4ab4da 100644 --- a/Include/cpython/objimpl.h +++ b/Include/cpython/objimpl.h @@ -97,6 +97,7 @@ typedef struct { #define _PyGC_SET_FINALIZED(o) \ _PyGCHead_SET_FINALIZED(_Py_AS_GC(o)) +PyAPI_FUNC(int) _PyObject_GC_IS_COLLECTING(PyObject *op); PyAPI_FUNC(PyObject *) _PyObject_GC_Malloc(size_t size); PyAPI_FUNC(PyObject *) _PyObject_GC_Calloc(size_t size); diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 134f6d168c9616..d904af712c56c1 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -69,10 +69,22 @@ module gc /* Get the object given the GC head */ #define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1)) +#define _PyGCHead_IS_COLLECTING(o) \ + (((o)->_gc_prev & PREV_MASK_COLLECTING) != 0) + +int _PyObject_GC_IS_COLLECTING(PyObject *op){ + if (PyObject_IS_GC(op)) { + PyGC_Head *gc = AS_GC(op); + return _PyGCHead_IS_COLLECTING(gc); + } else { + return 0; + } +} + static inline int gc_is_collecting(PyGC_Head *g) { - return (g->_gc_prev & PREV_MASK_COLLECTING) != 0; + return _PyGCHead_IS_COLLECTING(g); } static inline void diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index daee476444a4f7..c18eb66e921e2c 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -1,4 +1,5 @@ #include "Python.h" +#include "objimpl.h" #include "structmember.h" @@ -874,6 +875,35 @@ PyWeakref_GetObject(PyObject *ref) static void handle_callback(PyWeakReference *ref, PyObject *callback) { + /* If the garbage collector support is not properly implemented on + * some classes that are involved in a reference cycle, a weak + * reference may try to invoke a callback object that is being + * cleaned (tp_clear) by the garbage collector and it may be in an + * inconsistent state. As the garbage collector explicitly does + * not invoke callbacks that are part of the same cycle isolate (check + * PEP 442 for references about this terminology) as the weak reference + * (pretending that the weak reference was * destroyed first), we + * should act in the same way here. + * + * When running the garbage collector pass over a generation, is + * possible to end in this function if the tp_clear of a function + * decrements the references of some internal object that is + * weak-referenced, invoking the weak reference callback that will + * try to call the function, which is in an incosistent state as + * is in the middle of its tp_clear and some internal fields may + * be NULL. */ + + if (PyObject_IS_GC(callback) && _PyObject_GC_IS_COLLECTING(callback)) { + PyErr_WarnEx(PyExc_RuntimeWarning, "A weak reference" + " was trying to execute a callback to a function that is being cleared by" + " the garbage collector.\n A C extension class in the dependence" + " chain is probably not implementing the garbage collector support" + " correctly.", 1); + /* Return to avoid a potential crash when calling the callback that is in + * an invalid state */ + return; + } + PyObject *cbresult = _PyObject_CallOneArg(callback, (PyObject *)ref); if (cbresult == NULL)