bpo-9263: Dump Python object on GC assertion failure (GH-10062) · python/cpython@626bff8 (original) (raw)

1

1

`import unittest

`

2

2

`from test.support import (verbose, refcount_test, run_unittest,

`

3

3

`strip_python_stderr, cpython_only, start_threads,

`

4

``

`-

temp_dir, requires_type_collecting, TESTFN, unlink)

`

``

4

`+

temp_dir, requires_type_collecting, TESTFN, unlink,

`

``

5

`+

import_module)

`

5

6

`from test.support.script_helper import assert_python_ok, make_script

`

6

7

``

``

8

`+

import gc

`

7

9

`import sys

`

``

10

`+

import sysconfig

`

``

11

`+

import textwrap

`

``

12

`+

import threading

`

8

13

`import time

`

9

``

`-

import gc

`

10

14

`import weakref

`

11

``

`-

import threading

`

12

15

``

13

16

`try:

`

14

17

`from _testcapi import with_tp_del

`

`@@ -62,6 +65,14 @@ def init(self, partner=None):

`

62

65

`def tp_del(self):

`

63

66

`pass

`

64

67

``

``

68

`+

if sysconfig.get_config_vars().get('PY_CFLAGS', ''):

`

``

69

`+

BUILD_WITH_NDEBUG = ('-DNDEBUG' in sysconfig.get_config_vars()['PY_CFLAGS'])

`

``

70

`+

else:

`

``

71

`+

Usually, sys.gettotalrefcount() is only present if Python has been

`

``

72

`+

compiled in debug mode. If it's missing, expect that Python has

`

``

73

`+

been released in release mode: with NDEBUG defined.

`

``

74

`+

BUILD_WITH_NDEBUG = (not hasattr(sys, 'gettotalrefcount'))

`

``

75

+

65

76

`### Tests

`

66

77

`###############################################################################

`

67

78

``

`@@ -878,6 +889,58 @@ def test_collect_garbage(self):

`

878

889

`self.assertEqual(len(gc.garbage), 0)

`

879

890

``

880

891

``

``

892

`+

@unittest.skipIf(BUILD_WITH_NDEBUG,

`

``

893

`+

'built with -NDEBUG')

`

``

894

`+

def test_refcount_errors(self):

`

``

895

`+

self.preclean()

`

``

896

`+

Verify the "handling" of objects with broken refcounts

`

``

897

+

``

898

`+

Skip the test if ctypes is not available

`

``

899

`+

import_module("ctypes")

`

``

900

+

``

901

`+

import subprocess

`

``

902

`+

code = textwrap.dedent('''

`

``

903

`+

from test.support import gc_collect, SuppressCrashReport

`

``

904

+

``

905

`+

a = [1, 2, 3]

`

``

906

`+

b = [a]

`

``

907

+

``

908

`+

Avoid coredump when Py_FatalError() calls abort()

`

``

909

`+

SuppressCrashReport().enter()

`

``

910

+

``

911

`+

Simulate the refcount of "a" being too low (compared to the

`

``

912

`+

references held on it by live data), but keeping it above zero

`

``

913

`+

(to avoid deallocating it):

`

``

914

`+

import ctypes

`

``

915

`+

ctypes.pythonapi.Py_DecRef(ctypes.py_object(a))

`

``

916

+

``

917

`+

The garbage collector should now have a fatal error

`

``

918

`+

when it reaches the broken object

`

``

919

`+

gc_collect()

`

``

920

`+

''')

`

``

921

`+

p = subprocess.Popen([sys.executable, "-c", code],

`

``

922

`+

stdout=subprocess.PIPE,

`

``

923

`+

stderr=subprocess.PIPE)

`

``

924

`+

stdout, stderr = p.communicate()

`

``

925

`+

p.stdout.close()

`

``

926

`+

p.stderr.close()

`

``

927

`+

Verify that stderr has a useful error message:

`

``

928

`+

self.assertRegex(stderr,

`

``

929

`+

br'gcmodule.c:[0-9]+: gc_decref: Assertion "gc_get_refs(g) > 0" failed.')

`

``

930

`+

self.assertRegex(stderr,

`

``

931

`+

br'refcount is too small')

`

``

932

`+

self.assertRegex(stderr,

`

``

933

`+

br'object : [1, 2, 3]')

`

``

934

`+

self.assertRegex(stderr,

`

``

935

`+

br'type : list')

`

``

936

`+

self.assertRegex(stderr,

`

``

937

`+

br'refcount: 1')

`

``

938

`+

"address : 0x7fb5062efc18"

`

``

939

`+

"address : 7FB5062EFC18"

`

``

940

`+

self.assertRegex(stderr,

`

``

941

`+

br'address : [0-9a-fA-Fx]+')

`

``

942

+

``

943

+

881

944

`class GCTogglingTests(unittest.TestCase):

`

882

945

`def setUp(self):

`

883

946

`gc.enable()

`