]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-117174: Add a new route in linecache to fetch interactive source code (#117500)
authorPablo Galindo Salgado <Pablogsal@gmail.com>
Mon, 10 Mar 2025 21:54:05 +0000 (21:54 +0000)
committerGitHub <noreply@github.com>
Mon, 10 Mar 2025 21:54:05 +0000 (21:54 +0000)
16 files changed:
Lib/_pyrepl/console.py
Lib/_pyrepl/simple_interact.py
Lib/inspect.py
Lib/linecache.py
Lib/test/test_capi/test_exceptions.py
Lib/test/test_gdb/gdb_sample.py
Lib/test/test_gdb/test_backtrace.py
Lib/test/test_gdb/test_misc.py
Lib/test/test_gdb/test_pretty_print.py
Lib/test/test_gdb/util.py
Lib/test/test_io.py
Lib/test/test_linecache.py
Lib/test/test_repl.py
Lib/test/test_subprocess.py
Lib/traceback.py
Python/pythonrun.c

index db911b3e1f0b919047e8b449b5347085cefc58e1..7916638921bbf22ed53791b12af023dcf6595f99 100644 (file)
@@ -24,6 +24,7 @@ import _colorize  # type: ignore[import-not-found]
 from abc import ABC, abstractmethod
 import ast
 import code
+import linecache
 from dataclasses import dataclass, field
 import os.path
 import sys
@@ -205,6 +206,7 @@ class InteractiveColoredConsole(code.InteractiveConsole):
             item = wrapper([stmt])
             try:
                 code = self.compile.compiler(item, filename, the_symbol)
+                linecache._register_code(code, source, filename)
             except SyntaxError as e:
                 if e.args[0] == "'await' outside function":
                     python = os.path.basename(sys.executable)
index a065174ad42fb67ebd95a63dabaf545986a9cd77..6e46d7a3016751683d35459f000ba05edac3ecc3 100644 (file)
@@ -26,7 +26,6 @@ allowing multiline input and multiline history entries.
 from __future__ import annotations
 
 import _sitebuiltins
-import linecache
 import functools
 import os
 import sys
@@ -148,7 +147,6 @@ def run_multiline_interactive_console(
                 continue
 
             input_name = f"<python-input-{input_n}>"
-            linecache._register_code(input_name, statement, "<stdin>")  # type: ignore[attr-defined]
             more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single")  # type: ignore[call-arg]
             assert not more
             input_n += 1
index 182c2eedbaba643176b1dcc279876bf2cc8c5c9e..fcfe3b191ab5030119970bfa3ff81b4e976c4a34 100644 (file)
@@ -969,6 +969,8 @@ def findsource(object):
     module = getmodule(object, file)
     if module:
         lines = linecache.getlines(file, module.__dict__)
+        if not lines and file.startswith('<') and hasattr(object, "__code__"):
+            lines = linecache._getlines_from_code(object.__code__)
     else:
         lines = linecache.getlines(file)
     if not lines:
index 8ba2df73d5a8fb85250c581417768cf7bc4f675e..5d738ce520234fa5aa60f3c17bb4e86276c0c2d6 100644 (file)
@@ -11,6 +11,7 @@ __all__ = ["getline", "clearcache", "checkcache", "lazycache"]
 # The cache. Maps filenames to either a thunk which will provide source code,
 # or a tuple (size, mtime, lines, fullname) once loaded.
 cache = {}
+_interactive_cache = {}
 
 
 def clearcache():
@@ -44,6 +45,22 @@ def getlines(filename, module_globals=None):
         return []
 
 
+def _getline_from_code(filename, lineno):
+    lines = _getlines_from_code(filename)
+    if 1 <= lineno <= len(lines):
+        return lines[lineno - 1]
+    return ''
+
+
+def _getlines_from_code(code):
+    code_id = id(code)
+    if code_id in _interactive_cache:
+        entry = _interactive_cache[code_id]
+        if len(entry) != 1:
+            return _interactive_cache[code_id][2]
+    return []
+
+
 def checkcache(filename=None):
     """Discard cache entries that are out of date.
     (This is not checked upon each call!)"""
@@ -88,9 +105,13 @@ def updatecache(filename, module_globals=None):
     # These imports are not at top level because linecache is in the critical
     # path of the interpreter startup and importing os and sys take a lot of time
     # and slows down the startup sequence.
-    import os
-    import sys
-    import tokenize
+    try:
+        import os
+        import sys
+        import tokenize
+    except ImportError:
+        # These import can fail if the interpreter is shutting down
+        return []
 
     if filename in cache:
         if len(cache[filename]) != 1:
@@ -196,8 +217,14 @@ def lazycache(filename, module_globals):
 
 
 def _register_code(code, string, name):
-    cache[code] = (
-            len(string),
-            None,
-            [line + '\n' for line in string.splitlines()],
-            name)
+    entry = (len(string),
+             None,
+             [line + '\n' for line in string.splitlines()],
+             name)
+    stack = [code]
+    while stack:
+        code = stack.pop()
+        for const in code.co_consts:
+            if isinstance(const, type(code)):
+                stack.append(const)
+        _interactive_cache[id(code)] = entry
index 666e2f2ab095482954eefcf4bb9187238e2e7d36..ade55338e63b69fa7f6a31acc6faf99fe78cd063 100644 (file)
@@ -85,9 +85,7 @@ class Test_Exceptions(unittest.TestCase):
         warnings = proc.err.splitlines()
         self.assertEqual(warnings, [
             b'<string>:6: RuntimeWarning: Testing PyErr_WarnEx',
-            b'  foo()  # line 6',
             b'<string>:9: RuntimeWarning: Testing PyErr_WarnEx',
-            b'  foo()  # line 9',
         ])
 
     def test_warn_during_finalization(self):
index a7f23db73ea6e67f123292362992dded76e23653..375d8987fa80c3ae1650a5cac2cc78da8403c1a3 100644 (file)
@@ -1,4 +1,5 @@
 # Sample script for use by test_gdb
+from _typing import _idfunc
 
 def foo(a, b, c):
     bar(a=a, b=b, c=c)
@@ -7,6 +8,6 @@ def bar(a, b, c):
     baz(a, b, c)
 
 def baz(*args):
-    id(42)
+    _idfunc(42)
 
 foo(1, 2, 3)
index 714853c7b4732d95f2dd179429cbe829058a80cc..656f0c125313aa2f11dc84552a18fed01d3bcf3b 100644 (file)
@@ -20,14 +20,14 @@ class PyBtTests(DebuggerTests):
         self.assertMultilineMatches(bt,
                                     r'''^.*
 Traceback \(most recent call first\):
-  <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
+  <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
     baz\(a, b, c\)
-  File ".*gdb_sample.py", line 4, in foo
+  File ".*gdb_sample.py", line 5, in foo
     bar\(a=a, b=b, c=c\)
-  File ".*gdb_sample.py", line 12, in <module>
+  File ".*gdb_sample.py", line 13, in <module>
     foo\(1, 2, 3\)
 ''')
 
@@ -39,11 +39,11 @@ Traceback \(most recent call first\):
                                   cmds_after_breakpoint=['py-bt-full'])
         self.assertMultilineMatches(bt,
                                     r'''^.*
-#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 8, 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 4, in foo \(a=1, b=2, c=3\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 5, 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 12, in <module> \(\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 13, in <module> \(\)
     foo\(1, 2, 3\)
 ''')
 
@@ -55,6 +55,7 @@ Traceback \(most recent call first\):
         '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
@@ -70,7 +71,7 @@ for i in range(4):
    t[i].start()
 
 # Trigger a breakpoint on the main thread
-id(42)
+_idfunc(42)
 
 '''
         # Verify with "py-bt":
@@ -90,8 +91,8 @@ id(42)
     # 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\n'
-               'id(42)\n'
+        cmd = ('from gc import collect; from _typing import _idfunc\n'
+               '_idfunc(42)\n'
                'def foo():\n'
                '    collect()\n'
                'def bar():\n'
@@ -113,11 +114,12 @@ id(42)
                      "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()
 
-            id("first break point")
+            _idfunc("first break point")
             l = MyList()
         ''')
         cmds_after_breakpoint = ['break wrapper_call', 'continue']
index 1047f4867c1d038a122c746389668a3f7aaec2c0..b3dd24777cf1fbbef5c1e1055357c2760479ec00 100644 (file)
@@ -35,14 +35,14 @@ class PyListTests(DebuggerTests):
         bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
                                   cmds_after_breakpoint=['py-list'])
 
-        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',
+        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',
                            bt)
 
     def test_one_abs_arg(self):
@@ -50,25 +50,27 @@ class PyListTests(DebuggerTests):
         bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
                                   cmds_after_breakpoint=['py-list 9'])
 
-        self.assertListing('   9    def baz(*args):\n'
-                           ' >10        id(42)\n'
-                           '  11    \n'
-                           '  12    foo(1, 2, 3)\n',
+        self.assertListing('  10    def baz(*args):\n'
+                           ' >11        _idfunc(42)\n'
+                           '  12    \n'
+                           '  13    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,3'])
+                                  cmds_after_breakpoint=['py-list 1,4'])
 
         self.assertListing('   1    # Sample script for use by test_gdb\n'
-                           '   2    \n'
-                           '   3    def foo(a, b, c):\n',
+                           '   2    from _typing import _idfunc\n'
+                           '   3    \n'
+                           '   4    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)
@@ -77,7 +79,7 @@ def bar(a, b, c):
     pyobject_vectorcall(baz, (a, b, c), None)
 
 def baz(*args):
-    id(42)
+    _idfunc(42)
 
 foo(1, 2, 3)
 
@@ -94,7 +96,7 @@ class StackNavigationTests(DebuggerTests):
                                   cmds_after_breakpoint=['py-up', 'py-up'])
         self.assertMultilineMatches(bt,
                                     r'''^.*
-#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 13, in baz \(args=\(1, 2, 3\)\)
 #[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+>
 $''')
 
@@ -123,9 +125,9 @@ $''')
                                   cmds_after_breakpoint=['py-up', 'py-up', 'py-down'])
         self.assertMultilineMatches(bt,
                                     r'''^.*
-#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 13, 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 12, in baz \(args=\(1, 2, 3\)\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 13, in baz \(args=\(1, 2, 3\)\)
 $''')
 
 class PyPrintTests(DebuggerTests):
index dfc77d65ab16a408af93b2353330e2f6c6f95543..0ea714cea27674bb84bfafe186742da636e22751 100644 (file)
@@ -17,7 +17,7 @@ class PrettyPrintTests(DebuggerTests):
                      import_site=False):
         # Given an input python source representation of data,
         # run "python -c'id(DATA)'" under gdb with a breakpoint on
-        # builtin_id and scrape out gdb's representation of the "op"
+        # _typing__idfunc 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,6 +29,7 @@ class PrettyPrintTests(DebuggerTests):
         # 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)
@@ -36,10 +37,10 @@ class PrettyPrintTests(DebuggerTests):
         # in its output, depending on the width of the terminal it's connected
         # to (using its "wrap_here" function)
         m = re.search(
-            # Match '#0 builtin_id(self=..., v=...)'
-            r'#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)?\)'
+            # Match '#0 _typing_idfunc(module=..., x=...)'
+            r'#0\s+_typing__idfunc\s+\(module\=.*,\s+x=\s*(.*?)?\)'
             # Match ' at Python/bltinmodule.c'.
-            # bpo-38239: builtin_id() is defined in Python/bltinmodule.c,
+            # bpo-38239: typing_idfunc() is defined in Module/_typingmldule.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',
@@ -49,13 +50,13 @@ class PrettyPrintTests(DebuggerTests):
         return m.group(1), gdb_output
 
     def test_getting_backtrace(self):
-        gdb_output = self.get_stack_trace('id(42)')
+        gdb_output = self.get_stack_trace('from _typing import _idfunc;_idfunc(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('id(' + ascii(val) + ')')
+        gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(' + ascii(val) + ')')
         if not exp_repr:
             exp_repr = repr(val)
         self.assertEqual(gdb_repr, exp_repr,
@@ -173,7 +174,7 @@ class PrettyPrintTests(DebuggerTests):
         # which happens on deletion:
         gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
 s.remove('a')
-id(s)''')
+_idfunc(s)''')
         self.assertEqual(gdb_repr, "{'b'}")
 
     @support.requires_resource('cpu')
@@ -194,7 +195,7 @@ id(s)''')
 try:
     raise RuntimeError("I am an error")
 except RuntimeError as e:
-    id(e)
+    _idfunc(e)
 ''')
         self.assertEqual(gdb_repr,
                          "RuntimeError('I am an error',)")
@@ -205,7 +206,7 @@ except RuntimeError as e:
 try:
     a = 1 / 0
 except ZeroDivisionError as e:
-    id(e)
+    _idfunc(e)
 ''')
         self.assertEqual(gdb_repr,
                          "ZeroDivisionError('division by zero',)")
@@ -217,7 +218,7 @@ class Foo:
     pass
 foo = Foo()
 foo.an_int = 42
-id(foo)''')
+_idfunc(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)
@@ -230,7 +231,7 @@ class Foo(list):
 foo = Foo()
 foo += [1, 2, 3]
 foo.an_int = 42
-id(foo)''')
+_idfunc(foo)''')
         m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
 
         self.assertTrue(m,
@@ -245,7 +246,7 @@ class Foo(tuple):
     pass
 foo = Foo((1, 2, 3))
 foo.an_int = 42
-id(foo)''')
+_idfunc(foo)''')
         m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
 
         self.assertTrue(m,
@@ -283,8 +284,8 @@ id(foo)''')
     def test_NULL_ptr(self):
         'Ensure that a NULL PyObject* is handled gracefully'
         gdb_repr, gdb_output = (
-            self.get_gdb_repr('id(42)',
-                              cmds_after_breakpoint=['set variable v=0',
+            self.get_gdb_repr('_idfunc(42)',
+                              cmds_after_breakpoint=['set variable x=0',
                                                      'backtrace'])
             )
 
@@ -292,25 +293,25 @@ id(foo)''')
 
     def test_NULL_ob_type(self):
         'Ensure that a PyObject* with NULL ob_type is handled gracefully'
-        self.assertSane('id(42)',
-                        'set v->ob_type=0')
+        self.assertSane('_idfunc(42)',
+                        'set x->ob_type=0')
 
     def test_corrupt_ob_type(self):
         'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
-        self.assertSane('id(42)',
-                        'set v->ob_type=0xDEADBEEF',
+        self.assertSane('_idfunc(42)',
+                        'set x->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('id(42)',
-                        'set v->ob_type->tp_flags=0x0',
+        self.assertSane('_idfunc(42)',
+                        'set x->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('id(42)',
-                        'set v->ob_type->tp_name=0xDEADBEEF',
+        self.assertSane('_idfunc(42)',
+                        'set x->ob_type->tp_name=0xDEADBEEF',
                         exprepr='42')
 
     def test_builtins_help(self):
@@ -321,7 +322,7 @@ id(foo)''')
 
         # (this was the issue causing tracebacks in
         #  http://bugs.python.org/issue8032#msg100537 )
-        gdb_repr, gdb_output = self.get_gdb_repr('id(__builtins__.help)', import_site=True)
+        gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(__builtins__.help)', import_site=True)
 
         m = re.match(r'<_Helper\(\) at remote 0x-?[0-9a-f]+>', gdb_repr)
         self.assertTrue(m,
@@ -331,18 +332,18 @@ id(foo)''')
         '''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) ; id(a)")
+            self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; _idfunc(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) ; id(a)")
+            self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; _idfunc(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 ; id(a)")
+            self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; _idfunc(a)")
 
         self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}")
 
@@ -353,7 +354,7 @@ class Foo:
     pass
 foo = Foo()
 foo.an_attr = foo
-id(foo)''')
+_idfunc(foo)''')
         self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
                                  gdb_repr),
                         'Unexpected gdb representation: %r\n%s' % \
@@ -366,7 +367,7 @@ class Foo(object):
     pass
 foo = Foo()
 foo.an_attr = foo
-id(foo)''')
+_idfunc(foo)''')
         self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
                                  gdb_repr),
                         'Unexpected gdb representation: %r\n%s' % \
@@ -380,7 +381,7 @@ a = Foo()
 b = Foo()
 a.an_attr = b
 b.an_attr = a
-id(a)''')
+_idfunc(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' % \
@@ -388,7 +389,7 @@ id(a)''')
 
     def test_truncation(self):
         'Verify that very long output is truncated'
-        gdb_repr, gdb_output = self.get_gdb_repr('id(list(range(1000)))')
+        gdb_repr, gdb_output = self.get_gdb_repr('_idfunc(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, "
@@ -415,7 +416,7 @@ id(a)''')
                          1024 + len('...(truncated)'))
 
     def test_builtin_method(self):
-        gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)')
+        gdb_repr, gdb_output = self.get_gdb_repr('import sys; _idfunc(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' % \
@@ -424,15 +425,16 @@ id(a)''')
     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)
-id(f)''',
-                                          breakpoint='builtin_id',
-                                          cmds_after_breakpoint=['print (PyFrameObject*)v']
+_idfunc(f)''',
+                                          breakpoint='_typing__idfunc',
+                                          cmds_after_breakpoint=['print (PyFrameObject*)x']
                                           )
-        self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 4, in foo \(a=3.*',
+        self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 5, in foo \(a=3.*',
                                  gdb_output,
                                  re.DOTALL),
                         'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))
index 8097fd52ababe6cf6483fc0b64dd1c7557cb5e88..81b602ebdaf5170803def9cc7ebd5a89b18df8cc 100644 (file)
@@ -16,7 +16,7 @@ CHECKOUT_HOOK_PATH = os.path.join(os.path.dirname(sys.executable),
                                   'python-gdb.py')
 
 SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), 'gdb_sample.py')
-BREAKPOINT_FN = 'builtin_id'
+BREAKPOINT_FN = '_typing__idfunc'
 
 PYTHONHASHSEED = '123'
 
index 3b8ff1d20030b3b8570131ad96c8d1f13de746f0..23eb3b32b512f7bd970c88d7dc453ab2dd1c28af 100644 (file)
@@ -4558,11 +4558,11 @@ class MiscIOTest(unittest.TestCase):
         ''')
         proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code)
         warnings = proc.err.splitlines()
-        self.assertEqual(len(warnings), 4)
+        self.assertEqual(len(warnings), 2)
         self.assertTrue(
             warnings[0].startswith(b"<string>:5: EncodingWarning: "))
         self.assertTrue(
-            warnings[2].startswith(b"<string>:8: EncodingWarning: "))
+            warnings[1].startswith(b"<string>:8: EncodingWarning: "))
 
     def test_text_encoding(self):
         # PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8"
index 6f5955791407eab834cc198a975d80f2cc92e7d6..e23e1cc942856be1ac424a6c8b30974b9e4e298c 100644 (file)
@@ -8,6 +8,7 @@ import tokenize
 from importlib.machinery import ModuleSpec
 from test import support
 from test.support import os_helper
+from test.support.script_helper import assert_python_ok
 
 
 FILENAME = linecache.__file__
@@ -311,6 +312,12 @@ class LineCacheTests(unittest.TestCase):
         # just to be sure that we did not mess with cache
         linecache.clearcache()
 
+    def test_linecache_python_string(self):
+        cmdline = "import linecache;assert len(linecache.cache) == 0"
+        retcode, stdout, stderr = assert_python_ok('-c', cmdline)
+        self.assertEqual(retcode, 0)
+        self.assertEqual(stdout, b'')
+        self.assertEqual(stderr, b'')
 
 class LineCacheInvalidationTests(unittest.TestCase):
     def setUp(self):
index cb7b1938871657fea5bdf7ca85a174682db693b0..611fc89e57a9847f18b1cae9ef88685fd73f09e4 100644 (file)
@@ -213,7 +213,7 @@ class TestInteractiveInterpreter(unittest.TestCase):
         p.stdin.write(user_input)
         user_input2 = dedent("""
         import linecache
-        print(linecache.cache['<stdin>-1'])
+        print(linecache._interactive_cache[id(foo.__code__)])
         """)
         p.stdin.write(user_input2)
         output = kill_python(p)
index 645aa2cc7d9feae9d068f543b616f31f7e5ac5c0..81d97a88f07bdd88d00d248847690ebe6e55a437 100644 (file)
@@ -1804,9 +1804,9 @@ class RunFuncTestCase(BaseTestCase):
         cp = subprocess.run([sys.executable, "-Xwarn_default_encoding", "-c", code],
                             capture_output=True)
         lines = cp.stderr.splitlines()
-        self.assertEqual(len(lines), 4, lines)
+        self.assertEqual(len(lines), 2, lines)
         self.assertTrue(lines[0].startswith(b"<string>:2: EncodingWarning: "))
-        self.assertTrue(lines[2].startswith(b"<string>:3: EncodingWarning: "))
+        self.assertTrue(lines[1].startswith(b"<string>:3: EncodingWarning: "))
 
 
 def _get_test_grp_name():
index 38ea5b4bdccf6d7dc01fcaa71bcd0f39be0bceb6..647c23ed782c41a67d4f611cf828ff33cf61f098 100644 (file)
@@ -288,11 +288,11 @@ class FrameSummary:
     """
 
     __slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno',
-                 'name', '_lines', '_lines_dedented', 'locals')
+                 'name', '_lines', '_lines_dedented', 'locals', '_code')
 
     def __init__(self, filename, lineno, name, *, lookup_line=True,
             locals=None, line=None,
-            end_lineno=None, colno=None, end_colno=None):
+            end_lineno=None, colno=None, end_colno=None, **kwargs):
         """Construct a FrameSummary.
 
         :param lookup_line: If True, `linecache` is consulted for the source
@@ -308,6 +308,7 @@ class FrameSummary:
         self.colno = colno
         self.end_colno = end_colno
         self.name = name
+        self._code = kwargs.get("_code")
         self._lines = line
         self._lines_dedented = None
         if lookup_line:
@@ -347,7 +348,10 @@ class FrameSummary:
             lines = []
             for lineno in range(self.lineno, self.end_lineno + 1):
                 # treat errors (empty string) and empty lines (newline) as the same
-                lines.append(linecache.getline(self.filename, lineno).rstrip())
+                line = linecache.getline(self.filename, lineno).rstrip()
+                if not line and self._code is not None and self.filename.startswith("<"):
+                    line = linecache._getline_from_code(self._code, lineno).rstrip()
+                lines.append(line)
             self._lines = "\n".join(lines) + "\n"
 
     @property
@@ -484,9 +488,13 @@ class StackSummary(list):
                 f_locals = f.f_locals
             else:
                 f_locals = None
-            result.append(FrameSummary(
-                filename, lineno, name, lookup_line=False, locals=f_locals,
-                end_lineno=end_lineno, colno=colno, end_colno=end_colno))
+            result.append(
+                FrameSummary(filename, lineno, name,
+                    lookup_line=False, locals=f_locals,
+                    end_lineno=end_lineno, colno=colno, end_colno=end_colno,
+                    _code=f.f_code,
+                )
+            )
         for filename in fnames:
             linecache.checkcache(filename)
 
index 75d91b2040cdd5dd47cbe249486a8da85d438dbe..ebe222b736c50ef9e810bdebe6ae4ef404dd1a2d 100644 (file)
@@ -1410,7 +1410,7 @@ run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
 
         PyObject* result = PyObject_CallFunction(
             print_tb_func, "OOO",
-            interactive_filename,
+            co,
             interactive_src,
             filename
         );