Index: Include/object.h =================================================================== --- Include/object.h (revision 82908) +++ Include/object.h (working copy) @@ -860,6 +860,39 @@ else \ _PyTrash_deposit_object((PyObject*)op); +/* + Define an family of assertion macros that try to print the repr of a + particular object if the condition fails, and can be supplied a + crash-recording callback: +*/ +#ifdef NDEBUG +/* No debugging: compile away the assertions: */ +#define PY_OBJ_ASSERT_WITH_MSG_AND_CALLBACK(expr, obj, msg, cb) ((void)0) +#else +/* With debugging: generate checks: */ +#define PY_OBJ_ASSERT_WITH_MSG_AND_CALLBACK(expr, obj, msg, cb) \ + ((expr) \ + ? (void)(0) \ + : _PyObject_AssertFailed(obj, \ + msg, \ + cb, \ + __STRING(expr), \ + __FILE__, \ + __LINE__, \ + __PRETTY_FUNCTION__)) +#endif + +/* Define the simpler assertions in terms of the most detailed one: */ +#define PY_OBJ_ASSERT_WITH_MSG(expr, obj, msg) \ + PY_OBJ_ASSERT_WITH_MSG_AND_CALLBACK(expr, obj, msg, NULL) + +#define PY_OBJ_ASSERT(expr, obj) \ + PY_OBJ_ASSERT_WITH_MSG(expr, obj, NULL) + +#ifndef NDEBUG +PyAPI_FUNC(void) _PyObject_AssertFailed(PyObject *, const char *, void (*) (PyObject *), const char *, const char *, int, const char *); +#endif /* !NDEBUG */ + #ifdef __cplusplus } #endif Index: Objects/object.c =================================================================== --- Objects/object.c (revision 82908) +++ Objects/object.c (working copy) @@ -1895,6 +1895,35 @@ } } +#ifndef NDEBUG +PyAPI_FUNC(void) +_PyObject_AssertFailed(PyObject *obj, + const char *msg, + void (*cb) (PyObject *), + const char *expr, + const char *file, int line, const char *function) +{ + PySys_WriteStderr("%s:%d: %s: Assertion \"%s\" failed.\n", + file, line, function, expr); + if (msg) { + PySys_WriteStderr("%s\n", msg); + } + + 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); + } + + if (cb) { + (*cb)(obj); + } + + /* Terminate the process: */ + abort(); +} +#endif /* !NDEBUG */ + #ifdef __cplusplus } #endif Index: Modules/gcmodule.c =================================================================== --- Modules/gcmodule.c (revision 82908) +++ Modules/gcmodule.c (working copy) @@ -289,7 +289,8 @@ { PyGC_Head *gc = containers->gc.gc_next; for (; gc != containers; gc = gc->gc.gc_next) { - assert(gc->gc.gc_refs == GC_REACHABLE); + PY_OBJ_ASSERT(gc->gc.gc_refs == GC_REACHABLE, + FROM_GC(gc)); 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 @@ -309,7 +310,8 @@ * so serious that maybe this should be a release-build * check instead of an assert? */ - assert(gc->gc.gc_refs != 0); + PY_OBJ_ASSERT(gc->gc.gc_refs != 0, + FROM_GC(gc)); } } @@ -324,7 +326,9 @@ * 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 */ + PY_OBJ_ASSERT_WITH_MSG(gc->gc.gc_refs != 0, + FROM_GC(gc), + "refcount was too small"); if (gc->gc.gc_refs > 0) gc->gc.gc_refs--; } @@ -384,9 +388,10 @@ * If gc_refs == GC_UNTRACKED, it must be ignored. */ else { - assert(gc_refs > 0 - || gc_refs == GC_REACHABLE - || gc_refs == GC_UNTRACKED); + PY_OBJ_ASSERT(gc_refs > 0 + || gc_refs == GC_REACHABLE + || gc_refs == GC_UNTRACKED, + FROM_GC(gc)); } } return 0; @@ -428,7 +433,7 @@ */ PyObject *op = FROM_GC(gc); traverseproc traverse = Py_TYPE(op)->tp_traverse; - assert(gc->gc.gc_refs > 0); + PY_OBJ_ASSERT(gc->gc.gc_refs > 0, op); gc->gc.gc_refs = GC_REACHABLE; (void) traverse(op, (visitproc)visit_reachable, @@ -483,7 +488,8 @@ for (gc = unreachable->gc.gc_next; gc != unreachable; gc = next) { PyObject *op = FROM_GC(gc); - assert(IS_TENTATIVELY_UNREACHABLE(op)); + PY_OBJ_ASSERT(IS_TENTATIVELY_UNREACHABLE(op), op); + next = gc->gc.gc_next; if (has_finalizer(op)) { @@ -559,7 +565,7 @@ PyWeakReference **wrlist; op = FROM_GC(gc); - assert(IS_TENTATIVELY_UNREACHABLE(op)); + PY_OBJ_ASSERT(IS_TENTATIVELY_UNREACHABLE(op), op); next = gc->gc.gc_next; if (! PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) @@ -580,9 +586,9 @@ * the callback pointer intact. Obscure: it also * changes *wrlist. */ - assert(wr->wr_object == op); + PY_OBJ_ASSERT(wr->wr_object == op, wr->wr_object); _PyWeakref_ClearRef(wr); - assert(wr->wr_object == Py_None); + PY_OBJ_ASSERT(wr->wr_object == Py_None, wr->wr_object); if (wr->wr_callback == NULL) continue; /* no callback */ @@ -616,7 +622,7 @@ */ if (IS_TENTATIVELY_UNREACHABLE(wr)) continue; - assert(IS_REACHABLE(wr)); + PY_OBJ_ASSERT(IS_REACHABLE(wr), op); /* Create a new reference so that wr can't go away * before we can process it again. @@ -625,7 +631,8 @@ /* Move wr to wrcb_to_call, for the next pass. */ wrasgc = AS_GC(wr); - assert(wrasgc != next); /* wrasgc is reachable, but + PY_OBJ_ASSERT(wrasgc != next, op); + /* wrasgc is reachable, but next isn't, so they can't be the same */ gc_list_move(wrasgc, &wrcb_to_call); @@ -641,11 +648,11 @@ gc = wrcb_to_call.gc.gc_next; op = FROM_GC(gc); - assert(IS_REACHABLE(op)); - assert(PyWeakref_Check(op)); + PY_OBJ_ASSERT(IS_REACHABLE(op), op); + PY_OBJ_ASSERT(PyWeakref_Check(op), op); wr = (PyWeakReference *)op; callback = wr->wr_callback; - assert(callback != NULL); + PY_OBJ_ASSERT(callback != NULL, op); /* copy-paste of weakrefobject.c's handle_callback() */ temp = PyObject_CallFunctionObjArgs(callback, wr, NULL); @@ -729,7 +736,7 @@ PyGC_Head *gc = collectable->gc.gc_next; PyObject *op = FROM_GC(gc); - assert(IS_TENTATIVELY_UNREACHABLE(op)); + PY_OBJ_ASSERT(IS_TENTATIVELY_UNREACHABLE(op), op); if (debug & DEBUG_SAVEALL) { PyList_Append(garbage, op); }