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>
diff --git a/Objects/object.c b/Objects/object.c
index 8256071..2252f98 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -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 @@
--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. */