diff options
author | Victor Stinner <vstinner@redhat.com> | 2018-10-25 15:31:10 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-25 15:31:10 (GMT) |
commit | 626bff856840f471e98ec627043f780c111a063d (patch) | |
tree | 4c8f4da76f3443157e7a06a96d88bbc631650f88 /Lib | |
parent | 18618e652c56e61a134e596b315a13c7cb997a89 (diff) | |
download | cpython-626bff856840f471e98ec627043f780c111a063d.zip cpython-626bff856840f471e98ec627043f780c111a063d.tar.gz cpython-626bff856840f471e98ec627043f780c111a063d.tar.bz2 |
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>
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_gc.py | 69 |
1 files changed, 66 insertions, 3 deletions
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 8d806db..16b2242 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -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 gc import sys +import sysconfig +import textwrap +import threading import time -import gc import weakref -import threading 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() |