bpo-9263: Dump Python object on GC assertion failure (GH-10062)

Changes:

* Add _PyObject_AssertFailed() function.
* Add _PyObject_ASSERT() and _PyObject_ASSERT_WITH_MSG() macros.
* gc_decref(): replace assert() with _PyObject_ASSERT_WITH_MSG() to
  dump the faulty object if the assertion fails.

_PyObject_AssertFailed() calls:

* _PyMem_DumpTraceback(): try to log the traceback where the object
  memory has been allocated if tracemalloc is enabled.
* _PyObject_Dump(): log repr(obj).
* Py_FatalError(): log the current Python traceback.

_PyObject_AssertFailed() uses _PyObject_IsFreed() heuristic to check
if the object memory has been freed by a debug hook on Python memory
allocators.

Initial patch written by David Malcolm.

Co-Authored-By: David Malcolm <dmalcolm@redhat.com>
This commit is contained in:
Victor Stinner 2018-10-25 17:31:10 +02:00 committed by GitHub
parent 18618e652c
commit 626bff8568
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 180 additions and 14 deletions

View file

@ -10,6 +10,9 @@
extern "C" {
#endif
/* Defined in tracemalloc.c */
extern void _PyMem_DumpTraceback(int fd, const void *ptr);
_Py_IDENTIFIER(Py_Repr);
_Py_IDENTIFIER(__bytes__);
_Py_IDENTIFIER(__dir__);
@ -2212,6 +2215,55 @@ _PyTrash_thread_destroy_chain(void)
--tstate->trash_delete_nesting;
}
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",
file, line, function, expr);
fflush(stderr);
if (msg) {
fprintf(stderr, "; %s.\n", msg);
}
else {
fprintf(stderr, ".\n");
}
fflush(stderr);
if (obj == NULL) {
fprintf(stderr, "<NULL object>\n");
}
else if (_PyObject_IsFreed(obj)) {
/* It seems like the object memory has been freed:
don't access it to prevent a segmentation fault. */
fprintf(stderr, "<Freed object>\n");
}
else {
/* Diplay the traceback where the object has been allocated.
Do it before dumping repr(obj), since repr() is more likely
to crash than dumping the traceback. */
void *ptr;
PyTypeObject *type = Py_TYPE(obj);
if (PyType_IS_GC(type)) {
ptr = (void *)((char *)obj - sizeof(PyGC_Head));
}
else {
ptr = (void *)obj;
}
_PyMem_DumpTraceback(fileno(stderr), ptr);
/* 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);
}
fflush(stderr);
Py_FatalError("_PyObject_AssertFailed");
}
#ifndef Py_TRACE_REFS
/* For Py_LIMITED_API, we need an out-of-line version of _Py_Dealloc.
Define this here, so we can undefine the macro. */