diff options
author | Fedora Python maintainers <python-devel@lists.fedoraproject.org> | 2020-07-15 13:41:10 (GMT) |
---|---|---|
committer | Petr Viktorin <pviktori@redhat.com> | 2020-09-29 13:59:05 (GMT) |
commit | 0dcf06e1f9d02d0280928cf075fb362c5545b3c8 (patch) | |
tree | 5f5d4a77353143534e833203a20fb5c49f541619 /Modules | |
parent | 0f7555c31f403e763fd34726c720c4cafd6ef136 (diff) | |
download | cpython-0dcf06e1f9d02d0280928cf075fb362c5545b3c8.zip cpython-0dcf06e1f9d02d0280928cf075fb362c5545b3c8.tar.gz cpython-0dcf06e1f9d02d0280928cf075fb362c5545b3c8.tar.bz2 |
00170-gc-assertions.patch
00170 #
In debug builds, try to print repr() when a C-level assert fails in the
garbage collector (typically indicating a reference-counting error
somewhere else e.g in an extension module)
Backported to 2.7 from a patch I sent upstream for py3k
http://bugs.python.org/issue9263 (rhbz#614680)
hiding the proposed new macros/functions within gcmodule.c to avoid exposing
them within the extension API.
(rhbz#850013)
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/gcmodule.c | 108 |
1 files changed, 91 insertions, 17 deletions
diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 700c1a3..e2df829 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -21,6 +21,73 @@ #include "Python.h" #include "frameobject.h" /* for PyFrame_ClearFreeList */ +/* + Define a pair of assertion macros. + + These work like the regular C assert(), in that they will abort the + process with a message on stderr if the given condition fails to hold, + but compile away to nothing if NDEBUG is defined. + + However, before aborting, Python will also try to call _PyObject_Dump() on + the given object. This may be of use when investigating bugs in which a + particular object is corrupt (e.g. buggy a tp_visit method in an extension + module breaking the garbage collector), to help locate the broken objects. + + The WITH_MSG variant allows you to supply an additional message that Python + will attempt to print to stderr, after the object dump. +*/ +#ifdef NDEBUG +/* No debugging: compile away the assertions: */ +#define PyObject_ASSERT_WITH_MSG(obj, expr, msg) ((void)0) +#else +/* With debugging: generate checks: */ +#define PyObject_ASSERT_WITH_MSG(obj, expr, msg) \ + ((expr) \ + ? (void)(0) \ + : _PyObject_AssertFailed((obj), \ + (msg), \ + (__STRING(expr)), \ + (__FILE__), \ + (__LINE__), \ + (__PRETTY_FUNCTION__))) +#endif + +#define PyObject_ASSERT(obj, expr) \ + PyObject_ASSERT_WITH_MSG(obj, expr, NULL) + +static void _PyObject_AssertFailed(PyObject *, const char *, + const char *, const char *, int, + const char *); + +static void +_PyObject_AssertFailed(PyObject *obj, const char *msg, const char *expr, + const char *file, int line, const char *function) +{ + fprintf(stderr, + "%s:%d: %s: Assertion \"%s\" failed.\n", + file, line, function, expr); + if (msg) { + fprintf(stderr, "%s\n", msg); + } + + fflush(stderr); + + if (obj) { + /* This might succeed or fail, but we're about to abort, so at least + try to provide any extra info we can: */ + _PyObject_Dump(obj); + } + else { + fprintf(stderr, "NULL object\n"); + } + + fflush(stdout); + fflush(stderr); + + /* Terminate the process: */ + abort(); +} + /* Get an object's GC head */ #define AS_GC(o) ((PyGC_Head *)(o)-1) @@ -328,7 +395,8 @@ update_refs(PyGC_Head *containers) { PyGC_Head *gc = containers->gc.gc_next; for (; gc != containers; gc = gc->gc.gc_next) { - assert(gc->gc.gc_refs == GC_REACHABLE); + PyObject_ASSERT(FROM_GC(gc), + gc->gc.gc_refs == GC_REACHABLE); gc->gc.gc_refs = Py_REFCNT(FROM_GC(gc)); /* Python's cyclic gc should never see an incoming refcount * of 0: if something decref'ed to 0, it should have been @@ -348,7 +416,8 @@ update_refs(PyGC_Head *containers) * so serious that maybe this should be a release-build * check instead of an assert? */ - assert(gc->gc.gc_refs != 0); + PyObject_ASSERT(FROM_GC(gc), + gc->gc.gc_refs != 0); } } @@ -363,7 +432,9 @@ visit_decref(PyObject *op, void *data) * generation being collected, which can be recognized * because only they have positive gc_refs. */ - assert(gc->gc.gc_refs != 0); /* else refcount was too small */ + PyObject_ASSERT_WITH_MSG(FROM_GC(gc), + gc->gc.gc_refs != 0, + "refcount was too small"); if (gc->gc.gc_refs > 0) gc->gc.gc_refs--; } @@ -423,9 +494,10 @@ visit_reachable(PyObject *op, PyGC_Head *reachable) * If gc_refs == GC_UNTRACKED, it must be ignored. */ else { - assert(gc_refs > 0 - || gc_refs == GC_REACHABLE - || gc_refs == GC_UNTRACKED); + PyObject_ASSERT(FROM_GC(gc), + gc_refs > 0 + || gc_refs == GC_REACHABLE + || gc_refs == GC_UNTRACKED); } } return 0; @@ -467,7 +539,7 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) */ PyObject *op = FROM_GC(gc); traverseproc traverse = Py_TYPE(op)->tp_traverse; - assert(gc->gc.gc_refs > 0); + PyObject_ASSERT(op, gc->gc.gc_refs > 0); gc->gc.gc_refs = GC_REACHABLE; (void) traverse(op, (visitproc)visit_reachable, @@ -545,7 +617,8 @@ move_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) for (gc = unreachable->gc.gc_next; gc != unreachable; gc = next) { PyObject *op = FROM_GC(gc); - assert(IS_TENTATIVELY_UNREACHABLE(op)); + PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op)); + next = gc->gc.gc_next; if (has_finalizer(op)) { @@ -621,7 +694,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) PyWeakReference **wrlist; op = FROM_GC(gc); - assert(IS_TENTATIVELY_UNREACHABLE(op)); + PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op)); next = gc->gc.gc_next; if (! PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) @@ -642,9 +715,9 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) * the callback pointer intact. Obscure: it also * changes *wrlist. */ - assert(wr->wr_object == op); + PyObject_ASSERT(wr->wr_object, wr->wr_object == op); _PyWeakref_ClearRef(wr); - assert(wr->wr_object == Py_None); + PyObject_ASSERT(wr->wr_object, wr->wr_object == Py_None); if (wr->wr_callback == NULL) continue; /* no callback */ @@ -678,7 +751,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) */ if (IS_TENTATIVELY_UNREACHABLE(wr)) continue; - assert(IS_REACHABLE(wr)); + PyObject_ASSERT(op, IS_REACHABLE(wr)); /* Create a new reference so that wr can't go away * before we can process it again. @@ -687,7 +760,8 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) /* Move wr to wrcb_to_call, for the next pass. */ wrasgc = AS_GC(wr); - assert(wrasgc != next); /* wrasgc is reachable, but + PyObject_ASSERT(op, wrasgc != next); + /* wrasgc is reachable, but next isn't, so they can't be the same */ gc_list_move(wrasgc, &wrcb_to_call); @@ -703,11 +777,11 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) gc = wrcb_to_call.gc.gc_next; op = FROM_GC(gc); - assert(IS_REACHABLE(op)); - assert(PyWeakref_Check(op)); + PyObject_ASSERT(op, IS_REACHABLE(op)); + PyObject_ASSERT(op, PyWeakref_Check(op)); wr = (PyWeakReference *)op; callback = wr->wr_callback; - assert(callback != NULL); + PyObject_ASSERT(op, callback != NULL); /* copy-paste of weakrefobject.c's handle_callback() */ temp = PyObject_CallFunctionObjArgs(callback, wr, NULL); @@ -807,7 +881,7 @@ delete_garbage(PyGC_Head *collectable, PyGC_Head *old) PyGC_Head *gc = collectable->gc.gc_next; PyObject *op = FROM_GC(gc); - assert(IS_TENTATIVELY_UNREACHABLE(op)); + PyObject_ASSERT(op, IS_TENTATIVELY_UNREACHABLE(op)); if (debug & DEBUG_SAVEALL) { PyList_Append(garbage, op); } |