https://github.com/python/cpython/commit/0980e2e370ae4c5ec470b6137cf3c27cc2245684 commit: 0980e2e370ae4c5ec470b6137cf3c27cc2245684 branch: 3.13 author: Pablo Galindo Salgado <pablog...@gmail.com> committer: pablogsal <pablog...@gmail.com> date: 2025-03-11T23:43:07Z summary:
[3.13] gh-117174: Fix reference leak and gdb tests (GH-131095) (#131120) (cherry picked from commit ebc24d54bcf554403e9bf4b590d5c1f49e648e0d) files: M Lib/linecache.py M Lib/test/libregrtest/refleak.py M Lib/test/test_gdb/gdb_sample.py M Lib/test/test_gdb/test_backtrace.py M Lib/test/test_gdb/test_misc.py M Lib/test/test_gdb/test_pretty_print.py M Lib/test/test_gdb/util.py M Lib/test/test_repl.py diff --git a/Lib/linecache.py b/Lib/linecache.py index 5d738ce520234f..dc02de19eb62cb 100644 --- a/Lib/linecache.py +++ b/Lib/linecache.py @@ -51,9 +51,11 @@ def _getline_from_code(filename, lineno): return lines[lineno - 1] return '' +def _make_key(code): + return (code.co_filename, code.co_qualname, code.co_firstlineno) def _getlines_from_code(code): - code_id = id(code) + code_id = _make_key(code) if code_id in _interactive_cache: entry = _interactive_cache[code_id] if len(entry) != 1: @@ -215,7 +217,6 @@ def get_lines(name=name, *args, **kwargs): return True return False - def _register_code(code, string, name): entry = (len(string), None, @@ -227,4 +228,4 @@ def _register_code(code, string, name): for const in code.co_consts: if isinstance(const, type(code)): stack.append(const) - _interactive_cache[id(code)] = entry + _interactive_cache[_make_key(code)] = entry diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index 2e49b31e253d54..75fcd118d72699 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -3,6 +3,7 @@ import warnings from inspect import isabstract from typing import Any +import linecache from test import support from test.support import os_helper @@ -73,6 +74,11 @@ def runtest_refleak(test_name, test_func, ps = copyreg.dispatch_table.copy() pic = sys.path_importer_cache.copy() zdc: dict[str, Any] | None + # Linecache holds a cache with the source of interactive code snippets + # (e.g. code typed in the REPL). This cache is not cleared by + # linecache.clearcache(). We need to save and restore it to avoid false + # positives. + linecache_data = linecache.cache.copy(), linecache._interactive_cache.copy() # type: ignore[attr-defined] try: import zipimport except ImportError: @@ -122,7 +128,7 @@ def get_pooled_int(value): xml_filename = 'refleak-xml.tmp' result = None - dash_R_cleanup(fs, ps, pic, zdc, abcs) + dash_R_cleanup(fs, ps, pic, zdc, abcs, linecache_data) support.gc_collect() for i in rep_range: @@ -134,7 +140,7 @@ def get_pooled_int(value): refleak_helper._hunting_for_refleaks = current save_support_xml(xml_filename) - dash_R_cleanup(fs, ps, pic, zdc, abcs) + dash_R_cleanup(fs, ps, pic, zdc, abcs, linecache_data) support.gc_collect() # Read memory statistics immediately after the garbage collection. @@ -223,7 +229,7 @@ def check_fd_deltas(deltas): return (failed, result) -def dash_R_cleanup(fs, ps, pic, zdc, abcs): +def dash_R_cleanup(fs, ps, pic, zdc, abcs, linecache_data): import copyreg import collections.abc @@ -233,6 +239,11 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs): copyreg.dispatch_table.update(ps) sys.path_importer_cache.clear() sys.path_importer_cache.update(pic) + lcache, linteractive = linecache_data + linecache._interactive_cache.clear() + linecache._interactive_cache.update(linteractive) + linecache.cache.clear() + linecache.cache.update(lcache) try: import zipimport except ImportError: diff --git a/Lib/test/test_gdb/gdb_sample.py b/Lib/test/test_gdb/gdb_sample.py index 375d8987fa80c3..a7f23db73ea6e6 100644 --- a/Lib/test/test_gdb/gdb_sample.py +++ b/Lib/test/test_gdb/gdb_sample.py @@ -1,5 +1,4 @@ # Sample script for use by test_gdb -from _typing import _idfunc def foo(a, b, c): bar(a=a, b=b, c=c) @@ -8,6 +7,6 @@ def bar(a, b, c): baz(a, b, c) def baz(*args): - _idfunc(42) + id(42) foo(1, 2, 3) diff --git a/Lib/test/test_gdb/test_backtrace.py b/Lib/test/test_gdb/test_backtrace.py index 656f0c125313aa..714853c7b4732d 100644 --- a/Lib/test/test_gdb/test_backtrace.py +++ b/Lib/test/test_gdb/test_backtrace.py @@ -20,14 +20,14 @@ def test_bt(self): self.assertMultilineMatches(bt, r'''^.* Traceback \(most recent call first\): - <built-in method _idfunc of module object .*> - File ".*gdb_sample.py", line 11, in baz - _idfunc\(42\) - File ".*gdb_sample.py", line 8, in bar + <built-in method id of module object .*> + File ".*gdb_sample.py", line 10, in baz + id\(42\) + File ".*gdb_sample.py", line 7, in bar baz\(a, b, c\) - File ".*gdb_sample.py", line 5, in foo + File ".*gdb_sample.py", line 4, in foo bar\(a=a, b=b, c=c\) - File ".*gdb_sample.py", line 13, in <module> + File ".*gdb_sample.py", line 12, in <module> foo\(1, 2, 3\) ''') @@ -39,11 +39,11 @@ def test_bt_full(self): cmds_after_breakpoint=['py-bt-full']) self.assertMultilineMatches(bt, r'''^.* -#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 8, in bar \(a=1, b=2, c=3\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\) baz\(a, b, c\) -#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 5, in foo \(a=1, b=2, c=3\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\) bar\(a=a, b=b, c=c\) -#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 13, in <module> \(\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\) foo\(1, 2, 3\) ''') @@ -55,7 +55,6 @@ def test_threads(self): 'Verify that "py-bt" indicates threads that are waiting for the GIL' cmd = ''' from threading import Thread -from _typing import _idfunc class TestThread(Thread): # These threads would run forever, but we'll interrupt things with the @@ -71,7 +70,7 @@ def run(self): t[i].start() # Trigger a breakpoint on the main thread -_idfunc(42) +id(42) ''' # Verify with "py-bt": @@ -91,8 +90,8 @@ def run(self): # unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround def test_gc(self): 'Verify that "py-bt" indicates if a thread is garbage-collecting' - cmd = ('from gc import collect; from _typing import _idfunc\n' - '_idfunc(42)\n' + cmd = ('from gc import collect\n' + 'id(42)\n' 'def foo():\n' ' collect()\n' 'def bar():\n' @@ -114,12 +113,11 @@ def test_gc(self): "Python was compiled with optimizations") def test_wrapper_call(self): cmd = textwrap.dedent(''' - from typing import _idfunc class MyList(list): def __init__(self): super(*[]).__init__() # wrapper_call() - _idfunc("first break point") + id("first break point") l = MyList() ''') cmds_after_breakpoint = ['break wrapper_call', 'continue'] diff --git a/Lib/test/test_gdb/test_misc.py b/Lib/test/test_gdb/test_misc.py index b3dd24777cf1fb..1047f4867c1d03 100644 --- a/Lib/test/test_gdb/test_misc.py +++ b/Lib/test/test_gdb/test_misc.py @@ -35,14 +35,14 @@ def test_basic_command(self): bt = self.get_stack_trace(script=SAMPLE_SCRIPT, cmds_after_breakpoint=['py-list']) - self.assertListing(' 6 \n' - ' 7 def bar(a, b, c):\n' - ' 8 baz(a, b, c)\n' - ' 9 \n' - ' 10 def baz(*args):\n' - ' >11 _idfunc(42)\n' - ' 12 \n' - ' 13 foo(1, 2, 3)\n', + self.assertListing(' 5 \n' + ' 6 def bar(a, b, c):\n' + ' 7 baz(a, b, c)\n' + ' 8 \n' + ' 9 def baz(*args):\n' + ' >10 id(42)\n' + ' 11 \n' + ' 12 foo(1, 2, 3)\n', bt) def test_one_abs_arg(self): @@ -50,27 +50,25 @@ def test_one_abs_arg(self): bt = self.get_stack_trace(script=SAMPLE_SCRIPT, cmds_after_breakpoint=['py-list 9']) - self.assertListing(' 10 def baz(*args):\n' - ' >11 _idfunc(42)\n' - ' 12 \n' - ' 13 foo(1, 2, 3)\n', + self.assertListing(' 9 def baz(*args):\n' + ' >10 id(42)\n' + ' 11 \n' + ' 12 foo(1, 2, 3)\n', bt) def test_two_abs_args(self): 'Verify the "py-list" command with two absolute arguments' bt = self.get_stack_trace(script=SAMPLE_SCRIPT, - cmds_after_breakpoint=['py-list 1,4']) + cmds_after_breakpoint=['py-list 1,3']) self.assertListing(' 1 # Sample script for use by test_gdb\n' - ' 2 from _typing import _idfunc\n' - ' 3 \n' - ' 4 def foo(a, b, c):\n', + ' 2 \n' + ' 3 def foo(a, b, c):\n', bt) SAMPLE_WITH_C_CALL = """ from _testcapi import pyobject_vectorcall -from _typing import _idfunc def foo(a, b, c): bar(a, b, c) @@ -79,7 +77,7 @@ def bar(a, b, c): pyobject_vectorcall(baz, (a, b, c), None) def baz(*args): - _idfunc(42) + id(42) foo(1, 2, 3) @@ -96,7 +94,7 @@ def test_pyup_command(self): cmds_after_breakpoint=['py-up', 'py-up']) self.assertMultilineMatches(bt, r'''^.* -#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 13, in baz \(args=\(1, 2, 3\)\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\) #[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+> $''') @@ -125,9 +123,9 @@ def test_up_then_down(self): cmds_after_breakpoint=['py-up', 'py-up', 'py-down']) self.assertMultilineMatches(bt, r'''^.* -#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 13, in baz \(args=\(1, 2, 3\)\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\) #[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+> -#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 13, in baz \(args=\(1, 2, 3\)\) +#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\) $''') class PyPrintTests(DebuggerTests): diff --git a/Lib/test/test_gdb/test_pretty_print.py b/Lib/test/test_gdb/test_pretty_print.py index 0ea714cea27674..dfc77d65ab16a4 100644 --- a/Lib/test/test_gdb/test_pretty_print.py +++ b/Lib/test/test_gdb/test_pretty_print.py @@ -17,7 +17,7 @@ def get_gdb_repr(self, source, import_site=False): # Given an input python source representation of data, # run "python -c'id(DATA)'" under gdb with a breakpoint on - # _typing__idfunc and scrape out gdb's representation of the "op" + # builtin_id and scrape out gdb's representation of the "op" # parameter, and verify that the gdb displays the same string # # Verify that the gdb displays the expected string @@ -29,7 +29,6 @@ def get_gdb_repr(self, source, # undecodable characters may lurk there in optimized mode # (issue #19743). cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"] - source = "from _typing import _idfunc\n" + source gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN, cmds_after_breakpoint=cmds_after_breakpoint, import_site=import_site) @@ -37,10 +36,10 @@ def get_gdb_repr(self, source, # in its output, depending on the width of the terminal it's connected # to (using its "wrap_here" function) m = re.search( - # Match '#0 _typing_idfunc(module=..., x=...)' - r'#0\s+_typing__idfunc\s+\(module\=.*,\s+x=\s*(.*?)?\)' + # Match '#0 builtin_id(self=..., v=...)' + r'#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)?\)' # Match ' at Python/bltinmodule.c'. - # bpo-38239: typing_idfunc() is defined in Module/_typingmldule.c, + # bpo-38239: builtin_id() is defined in Python/bltinmodule.c, # but accept any "Directory\file.c" to support Link Time # Optimization (LTO). r'\s+at\s+\S*[A-Za-z]+/[A-Za-z0-9_-]+\.c', @@ -50,13 +49,13 @@ def get_gdb_repr(self, source, return m.group(1), gdb_output def test_getting_backtrace(self): - gdb_output = self.get_stack_trace('from _typing import _idfunc;_idfunc(42)') + gdb_output = self.get_stack_trace('id(42)') self.assertTrue(BREAKPOINT_FN in gdb_output) def assertGdbRepr(self, val, exp_repr=None): # Ensure that gdb's rendering of the value in a debugged process # matches repr(value) in this process: - gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(' + ascii(val) + ')') + gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')') if not exp_repr: exp_repr = repr(val) self.assertEqual(gdb_repr, exp_repr, @@ -174,7 +173,7 @@ def test_sets(self): # which happens on deletion: gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b']) s.remove('a') -_idfunc(s)''') +id(s)''') self.assertEqual(gdb_repr, "{'b'}") @support.requires_resource('cpu') @@ -195,7 +194,7 @@ def test_exceptions(self): try: raise RuntimeError("I am an error") except RuntimeError as e: - _idfunc(e) + id(e) ''') self.assertEqual(gdb_repr, "RuntimeError('I am an error',)") @@ -206,7 +205,7 @@ def test_exceptions(self): try: a = 1 / 0 except ZeroDivisionError as e: - _idfunc(e) + id(e) ''') self.assertEqual(gdb_repr, "ZeroDivisionError('division by zero',)") @@ -218,7 +217,7 @@ class Foo: pass foo = Foo() foo.an_int = 42 -_idfunc(foo)''') +id(foo)''') m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr) self.assertTrue(m, msg='Unexpected new-style class rendering %r' % gdb_repr) @@ -231,7 +230,7 @@ class Foo(list): foo = Foo() foo += [1, 2, 3] foo.an_int = 42 -_idfunc(foo)''') +id(foo)''') m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr) self.assertTrue(m, @@ -246,7 +245,7 @@ class Foo(tuple): pass foo = Foo((1, 2, 3)) foo.an_int = 42 -_idfunc(foo)''') +id(foo)''') m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr) self.assertTrue(m, @@ -284,8 +283,8 @@ def assertSane(self, source, corruption, exprepr=None): def test_NULL_ptr(self): 'Ensure that a NULL PyObject* is handled gracefully' gdb_repr, gdb_output = ( - self.get_gdb_repr('_idfunc(42)', - cmds_after_breakpoint=['set variable x=0', + self.get_gdb_repr('id(42)', + cmds_after_breakpoint=['set variable v=0', 'backtrace']) ) @@ -293,25 +292,25 @@ def test_NULL_ptr(self): def test_NULL_ob_type(self): 'Ensure that a PyObject* with NULL ob_type is handled gracefully' - self.assertSane('_idfunc(42)', - 'set x->ob_type=0') + self.assertSane('id(42)', + 'set v->ob_type=0') def test_corrupt_ob_type(self): 'Ensure that a PyObject* with a corrupt ob_type is handled gracefully' - self.assertSane('_idfunc(42)', - 'set x->ob_type=0xDEADBEEF', + self.assertSane('id(42)', + 'set v->ob_type=0xDEADBEEF', exprepr='42') def test_corrupt_tp_flags(self): 'Ensure that a PyObject* with a type with corrupt tp_flags is handled' - self.assertSane('_idfunc(42)', - 'set x->ob_type->tp_flags=0x0', + self.assertSane('id(42)', + 'set v->ob_type->tp_flags=0x0', exprepr='42') def test_corrupt_tp_name(self): 'Ensure that a PyObject* with a type with corrupt tp_name is handled' - self.assertSane('_idfunc(42)', - 'set x->ob_type->tp_name=0xDEADBEEF', + self.assertSane('id(42)', + 'set v->ob_type->tp_name=0xDEADBEEF', exprepr='42') def test_builtins_help(self): @@ -322,7 +321,7 @@ def test_builtins_help(self): # (this was the issue causing tracebacks in # http://bugs.python.org/issue8032#msg100537 ) - gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(__builtins__.help)', import_site=True) + gdb_repr, gdb_output = self.get_gdb_repr('id(__builtins__.help)', import_site=True) m = re.match(r'<_Helper\(\) at remote 0x-?[0-9a-f]+>', gdb_repr) self.assertTrue(m, @@ -332,18 +331,18 @@ def test_selfreferential_list(self): '''Ensure that a reference loop involving a list doesn't lead proxyval into an infinite loop:''' gdb_repr, gdb_output = \ - self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; _idfunc(a)") + self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; id(a)") self.assertEqual(gdb_repr, '[3, 4, 5, [...]]') gdb_repr, gdb_output = \ - self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; _idfunc(a)") + self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; id(a)") self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]') def test_selfreferential_dict(self): '''Ensure that a reference loop involving a dict doesn't lead proxyval into an infinite loop:''' gdb_repr, gdb_output = \ - self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; _idfunc(a)") + self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; id(a)") self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}") @@ -354,7 +353,7 @@ class Foo: pass foo = Foo() foo.an_attr = foo -_idfunc(foo)''') +id(foo)''') self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>', gdb_repr), 'Unexpected gdb representation: %r\n%s' % \ @@ -367,7 +366,7 @@ class Foo(object): pass foo = Foo() foo.an_attr = foo -_idfunc(foo)''') +id(foo)''') self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>', gdb_repr), 'Unexpected gdb representation: %r\n%s' % \ @@ -381,7 +380,7 @@ class Foo(object): b = Foo() a.an_attr = b b.an_attr = a -_idfunc(a)''') +id(a)''') self.assertTrue(re.match(r'<Foo\(an_attr=<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>\) at remote 0x-?[0-9a-f]+>', gdb_repr), 'Unexpected gdb representation: %r\n%s' % \ @@ -389,7 +388,7 @@ class Foo(object): def test_truncation(self): 'Verify that very long output is truncated' - gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(list(range(1000)))') + gdb_repr, gdb_output = self.get_gdb_repr('id(list(range(1000)))') self.assertEqual(gdb_repr, "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, " "14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, " @@ -416,7 +415,7 @@ def test_truncation(self): 1024 + len('...(truncated)')) def test_builtin_method(self): - gdb_repr, gdb_output = self.get_gdb_repr('import sys; _idfunc(sys.stdout.readlines)') + gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)') self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>', gdb_repr), 'Unexpected gdb representation: %r\n%s' % \ @@ -425,16 +424,15 @@ def test_builtin_method(self): def test_frames(self): gdb_output = self.get_stack_trace(''' import sys -from _typing import _idfunc def foo(a, b, c): return sys._getframe(0) f = foo(3, 4, 5) -_idfunc(f)''', - breakpoint='_typing__idfunc', - cmds_after_breakpoint=['print (PyFrameObject*)x'] +id(f)''', + breakpoint='builtin_id', + cmds_after_breakpoint=['print (PyFrameObject*)v'] ) - self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 5, in foo \(a=3.*', + self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 4, in foo \(a=3.*', gdb_output, re.DOTALL), 'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output)) diff --git a/Lib/test/test_gdb/util.py b/Lib/test/test_gdb/util.py index 694774bc5ab702..54c6b2de7cc99d 100644 --- a/Lib/test/test_gdb/util.py +++ b/Lib/test/test_gdb/util.py @@ -17,7 +17,7 @@ 'python-gdb.py') SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), 'gdb_sample.py') -BREAKPOINT_FN = '_typing__idfunc' +BREAKPOINT_FN = 'builtin_id' PYTHONHASHSEED = '123' diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 66262d352c2ec2..fdaff5b99bb803 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -213,7 +213,7 @@ def bar(x): p.stdin.write(user_input) user_input2 = dedent(""" import linecache - print(linecache._interactive_cache[id(foo.__code__)]) + print(linecache._interactive_cache[linecache._make_key(foo.__code__)]) """) p.stdin.write(user_input2) output = kill_python(p) _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com