mirror of
https://github.com/python/cpython.git
synced 2025-11-02 03:01:58 +00:00
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:
parent
18618e652c
commit
626bff8568
6 changed files with 180 additions and 14 deletions
|
|
@ -1,14 +1,17 @@
|
|||
import unittest
|
||||
from test.support import (verbose, refcount_test, run_unittest,
|
||||
strip_python_stderr, cpython_only, start_threads,
|
||||
temp_dir, requires_type_collecting, TESTFN, unlink)
|
||||
temp_dir, requires_type_collecting, TESTFN, unlink,
|
||||
import_module)
|
||||
from test.support.script_helper import assert_python_ok, make_script
|
||||
|
||||
import sys
|
||||
import time
|
||||
import gc
|
||||
import weakref
|
||||
import sys
|
||||
import sysconfig
|
||||
import textwrap
|
||||
import threading
|
||||
import time
|
||||
import weakref
|
||||
|
||||
try:
|
||||
from _testcapi import with_tp_del
|
||||
|
|
@ -62,6 +65,14 @@ class Uncollectable(object):
|
|||
def __tp_del__(self):
|
||||
pass
|
||||
|
||||
if sysconfig.get_config_vars().get('PY_CFLAGS', ''):
|
||||
BUILD_WITH_NDEBUG = ('-DNDEBUG' in sysconfig.get_config_vars()['PY_CFLAGS'])
|
||||
else:
|
||||
# Usually, sys.gettotalrefcount() is only present if Python has been
|
||||
# compiled in debug mode. If it's missing, expect that Python has
|
||||
# been released in release mode: with NDEBUG defined.
|
||||
BUILD_WITH_NDEBUG = (not hasattr(sys, 'gettotalrefcount'))
|
||||
|
||||
### Tests
|
||||
###############################################################################
|
||||
|
||||
|
|
@ -878,6 +889,58 @@ class GCCallbackTests(unittest.TestCase):
|
|||
self.assertEqual(len(gc.garbage), 0)
|
||||
|
||||
|
||||
@unittest.skipIf(BUILD_WITH_NDEBUG,
|
||||
'built with -NDEBUG')
|
||||
def test_refcount_errors(self):
|
||||
self.preclean()
|
||||
# Verify the "handling" of objects with broken refcounts
|
||||
|
||||
# Skip the test if ctypes is not available
|
||||
import_module("ctypes")
|
||||
|
||||
import subprocess
|
||||
code = textwrap.dedent('''
|
||||
from test.support import gc_collect, SuppressCrashReport
|
||||
|
||||
a = [1, 2, 3]
|
||||
b = [a]
|
||||
|
||||
# Avoid coredump when Py_FatalError() calls abort()
|
||||
SuppressCrashReport().__enter__()
|
||||
|
||||
# Simulate the refcount of "a" being too low (compared to the
|
||||
# references held on it by live data), but keeping it above zero
|
||||
# (to avoid deallocating it):
|
||||
import ctypes
|
||||
ctypes.pythonapi.Py_DecRef(ctypes.py_object(a))
|
||||
|
||||
# The garbage collector should now have a fatal error
|
||||
# when it reaches the broken object
|
||||
gc_collect()
|
||||
''')
|
||||
p = subprocess.Popen([sys.executable, "-c", code],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
p.stdout.close()
|
||||
p.stderr.close()
|
||||
# Verify that stderr has a useful error message:
|
||||
self.assertRegex(stderr,
|
||||
br'gcmodule\.c:[0-9]+: gc_decref: Assertion "gc_get_refs\(g\) > 0" failed.')
|
||||
self.assertRegex(stderr,
|
||||
br'refcount is too small')
|
||||
self.assertRegex(stderr,
|
||||
br'object : \[1, 2, 3\]')
|
||||
self.assertRegex(stderr,
|
||||
br'type : list')
|
||||
self.assertRegex(stderr,
|
||||
br'refcount: 1')
|
||||
# "address : 0x7fb5062efc18"
|
||||
# "address : 7FB5062EFC18"
|
||||
self.assertRegex(stderr,
|
||||
br'address : [0-9a-fA-Fx]+')
|
||||
|
||||
|
||||
class GCTogglingTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
gc.enable()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue