mirror of
https://github.com/python/cpython.git
synced 2025-09-26 10:19:53 +00:00
gh-117174: Add a new route in linecache to fetch interactive source code (#117500)
This commit is contained in:
parent
ecdf6b15b0
commit
a931a8b324
16 changed files with 142 additions and 93 deletions
|
@ -24,6 +24,7 @@ import _colorize # type: ignore[import-not-found]
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
import ast
|
import ast
|
||||||
import code
|
import code
|
||||||
|
import linecache
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
|
@ -205,6 +206,7 @@ class InteractiveColoredConsole(code.InteractiveConsole):
|
||||||
item = wrapper([stmt])
|
item = wrapper([stmt])
|
||||||
try:
|
try:
|
||||||
code = self.compile.compiler(item, filename, the_symbol)
|
code = self.compile.compiler(item, filename, the_symbol)
|
||||||
|
linecache._register_code(code, source, filename)
|
||||||
except SyntaxError as e:
|
except SyntaxError as e:
|
||||||
if e.args[0] == "'await' outside function":
|
if e.args[0] == "'await' outside function":
|
||||||
python = os.path.basename(sys.executable)
|
python = os.path.basename(sys.executable)
|
||||||
|
|
|
@ -26,7 +26,6 @@ allowing multiline input and multiline history entries.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import _sitebuiltins
|
import _sitebuiltins
|
||||||
import linecache
|
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -148,7 +147,6 @@ def run_multiline_interactive_console(
|
||||||
continue
|
continue
|
||||||
|
|
||||||
input_name = f"<python-input-{input_n}>"
|
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]
|
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg]
|
||||||
assert not more
|
assert not more
|
||||||
input_n += 1
|
input_n += 1
|
||||||
|
|
|
@ -969,6 +969,8 @@ def findsource(object):
|
||||||
module = getmodule(object, file)
|
module = getmodule(object, file)
|
||||||
if module:
|
if module:
|
||||||
lines = linecache.getlines(file, module.__dict__)
|
lines = linecache.getlines(file, module.__dict__)
|
||||||
|
if not lines and file.startswith('<') and hasattr(object, "__code__"):
|
||||||
|
lines = linecache._getlines_from_code(object.__code__)
|
||||||
else:
|
else:
|
||||||
lines = linecache.getlines(file)
|
lines = linecache.getlines(file)
|
||||||
if not lines:
|
if not lines:
|
||||||
|
|
|
@ -11,6 +11,7 @@ __all__ = ["getline", "clearcache", "checkcache", "lazycache"]
|
||||||
# The cache. Maps filenames to either a thunk which will provide source code,
|
# The cache. Maps filenames to either a thunk which will provide source code,
|
||||||
# or a tuple (size, mtime, lines, fullname) once loaded.
|
# or a tuple (size, mtime, lines, fullname) once loaded.
|
||||||
cache = {}
|
cache = {}
|
||||||
|
_interactive_cache = {}
|
||||||
|
|
||||||
|
|
||||||
def clearcache():
|
def clearcache():
|
||||||
|
@ -44,6 +45,22 @@ def getlines(filename, module_globals=None):
|
||||||
return []
|
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):
|
def checkcache(filename=None):
|
||||||
"""Discard cache entries that are out of date.
|
"""Discard cache entries that are out of date.
|
||||||
(This is not checked upon each call!)"""
|
(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
|
# 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
|
# path of the interpreter startup and importing os and sys take a lot of time
|
||||||
# and slows down the startup sequence.
|
# and slows down the startup sequence.
|
||||||
import os
|
try:
|
||||||
import sys
|
import os
|
||||||
import tokenize
|
import sys
|
||||||
|
import tokenize
|
||||||
|
except ImportError:
|
||||||
|
# These import can fail if the interpreter is shutting down
|
||||||
|
return []
|
||||||
|
|
||||||
if filename in cache:
|
if filename in cache:
|
||||||
if len(cache[filename]) != 1:
|
if len(cache[filename]) != 1:
|
||||||
|
@ -196,8 +217,14 @@ def lazycache(filename, module_globals):
|
||||||
|
|
||||||
|
|
||||||
def _register_code(code, string, name):
|
def _register_code(code, string, name):
|
||||||
cache[code] = (
|
entry = (len(string),
|
||||||
len(string),
|
None,
|
||||||
None,
|
[line + '\n' for line in string.splitlines()],
|
||||||
[line + '\n' for line in string.splitlines()],
|
name)
|
||||||
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
|
||||||
|
|
|
@ -85,9 +85,7 @@ class Test_Exceptions(unittest.TestCase):
|
||||||
warnings = proc.err.splitlines()
|
warnings = proc.err.splitlines()
|
||||||
self.assertEqual(warnings, [
|
self.assertEqual(warnings, [
|
||||||
b'<string>:6: RuntimeWarning: Testing PyErr_WarnEx',
|
b'<string>:6: RuntimeWarning: Testing PyErr_WarnEx',
|
||||||
b' foo() # line 6',
|
|
||||||
b'<string>:9: RuntimeWarning: Testing PyErr_WarnEx',
|
b'<string>:9: RuntimeWarning: Testing PyErr_WarnEx',
|
||||||
b' foo() # line 9',
|
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_warn_during_finalization(self):
|
def test_warn_during_finalization(self):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Sample script for use by test_gdb
|
# Sample script for use by test_gdb
|
||||||
|
from _typing import _idfunc
|
||||||
|
|
||||||
def foo(a, b, c):
|
def foo(a, b, c):
|
||||||
bar(a=a, b=b, c=c)
|
bar(a=a, b=b, c=c)
|
||||||
|
@ -7,6 +8,6 @@ def bar(a, b, c):
|
||||||
baz(a, b, c)
|
baz(a, b, c)
|
||||||
|
|
||||||
def baz(*args):
|
def baz(*args):
|
||||||
id(42)
|
_idfunc(42)
|
||||||
|
|
||||||
foo(1, 2, 3)
|
foo(1, 2, 3)
|
||||||
|
|
|
@ -20,14 +20,14 @@ class PyBtTests(DebuggerTests):
|
||||||
self.assertMultilineMatches(bt,
|
self.assertMultilineMatches(bt,
|
||||||
r'''^.*
|
r'''^.*
|
||||||
Traceback \(most recent call first\):
|
Traceback \(most recent call first\):
|
||||||
<built-in method id of module object .*>
|
<built-in method _idfunc of module object .*>
|
||||||
File ".*gdb_sample.py", line 10, in baz
|
File ".*gdb_sample.py", line 11, in baz
|
||||||
id\(42\)
|
_idfunc\(42\)
|
||||||
File ".*gdb_sample.py", line 7, in bar
|
File ".*gdb_sample.py", line 8, in bar
|
||||||
baz\(a, b, c\)
|
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\)
|
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\)
|
foo\(1, 2, 3\)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
@ -39,11 +39,11 @@ Traceback \(most recent call first\):
|
||||||
cmds_after_breakpoint=['py-bt-full'])
|
cmds_after_breakpoint=['py-bt-full'])
|
||||||
self.assertMultilineMatches(bt,
|
self.assertMultilineMatches(bt,
|
||||||
r'''^.*
|
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\)
|
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\)
|
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\)
|
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'
|
'Verify that "py-bt" indicates threads that are waiting for the GIL'
|
||||||
cmd = '''
|
cmd = '''
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from _typing import _idfunc
|
||||||
|
|
||||||
class TestThread(Thread):
|
class TestThread(Thread):
|
||||||
# These threads would run forever, but we'll interrupt things with the
|
# These threads would run forever, but we'll interrupt things with the
|
||||||
|
@ -70,7 +71,7 @@ for i in range(4):
|
||||||
t[i].start()
|
t[i].start()
|
||||||
|
|
||||||
# Trigger a breakpoint on the main thread
|
# Trigger a breakpoint on the main thread
|
||||||
id(42)
|
_idfunc(42)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# Verify with "py-bt":
|
# Verify with "py-bt":
|
||||||
|
@ -90,8 +91,8 @@ id(42)
|
||||||
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
|
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
|
||||||
def test_gc(self):
|
def test_gc(self):
|
||||||
'Verify that "py-bt" indicates if a thread is garbage-collecting'
|
'Verify that "py-bt" indicates if a thread is garbage-collecting'
|
||||||
cmd = ('from gc import collect\n'
|
cmd = ('from gc import collect; from _typing import _idfunc\n'
|
||||||
'id(42)\n'
|
'_idfunc(42)\n'
|
||||||
'def foo():\n'
|
'def foo():\n'
|
||||||
' collect()\n'
|
' collect()\n'
|
||||||
'def bar():\n'
|
'def bar():\n'
|
||||||
|
@ -113,11 +114,12 @@ id(42)
|
||||||
"Python was compiled with optimizations")
|
"Python was compiled with optimizations")
|
||||||
def test_wrapper_call(self):
|
def test_wrapper_call(self):
|
||||||
cmd = textwrap.dedent('''
|
cmd = textwrap.dedent('''
|
||||||
|
from typing import _idfunc
|
||||||
class MyList(list):
|
class MyList(list):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(*[]).__init__() # wrapper_call()
|
super(*[]).__init__() # wrapper_call()
|
||||||
|
|
||||||
id("first break point")
|
_idfunc("first break point")
|
||||||
l = MyList()
|
l = MyList()
|
||||||
''')
|
''')
|
||||||
cmds_after_breakpoint = ['break wrapper_call', 'continue']
|
cmds_after_breakpoint = ['break wrapper_call', 'continue']
|
||||||
|
|
|
@ -35,14 +35,14 @@ class PyListTests(DebuggerTests):
|
||||||
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
|
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
|
||||||
cmds_after_breakpoint=['py-list'])
|
cmds_after_breakpoint=['py-list'])
|
||||||
|
|
||||||
self.assertListing(' 5 \n'
|
self.assertListing(' 6 \n'
|
||||||
' 6 def bar(a, b, c):\n'
|
' 7 def bar(a, b, c):\n'
|
||||||
' 7 baz(a, b, c)\n'
|
' 8 baz(a, b, c)\n'
|
||||||
' 8 \n'
|
' 9 \n'
|
||||||
' 9 def baz(*args):\n'
|
' 10 def baz(*args):\n'
|
||||||
' >10 id(42)\n'
|
' >11 _idfunc(42)\n'
|
||||||
' 11 \n'
|
' 12 \n'
|
||||||
' 12 foo(1, 2, 3)\n',
|
' 13 foo(1, 2, 3)\n',
|
||||||
bt)
|
bt)
|
||||||
|
|
||||||
def test_one_abs_arg(self):
|
def test_one_abs_arg(self):
|
||||||
|
@ -50,25 +50,27 @@ class PyListTests(DebuggerTests):
|
||||||
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
|
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
|
||||||
cmds_after_breakpoint=['py-list 9'])
|
cmds_after_breakpoint=['py-list 9'])
|
||||||
|
|
||||||
self.assertListing(' 9 def baz(*args):\n'
|
self.assertListing(' 10 def baz(*args):\n'
|
||||||
' >10 id(42)\n'
|
' >11 _idfunc(42)\n'
|
||||||
' 11 \n'
|
' 12 \n'
|
||||||
' 12 foo(1, 2, 3)\n',
|
' 13 foo(1, 2, 3)\n',
|
||||||
bt)
|
bt)
|
||||||
|
|
||||||
def test_two_abs_args(self):
|
def test_two_abs_args(self):
|
||||||
'Verify the "py-list" command with two absolute arguments'
|
'Verify the "py-list" command with two absolute arguments'
|
||||||
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
|
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'
|
self.assertListing(' 1 # Sample script for use by test_gdb\n'
|
||||||
' 2 \n'
|
' 2 from _typing import _idfunc\n'
|
||||||
' 3 def foo(a, b, c):\n',
|
' 3 \n'
|
||||||
|
' 4 def foo(a, b, c):\n',
|
||||||
bt)
|
bt)
|
||||||
|
|
||||||
SAMPLE_WITH_C_CALL = """
|
SAMPLE_WITH_C_CALL = """
|
||||||
|
|
||||||
from _testcapi import pyobject_vectorcall
|
from _testcapi import pyobject_vectorcall
|
||||||
|
from _typing import _idfunc
|
||||||
|
|
||||||
def foo(a, b, c):
|
def foo(a, b, c):
|
||||||
bar(a, b, c)
|
bar(a, b, c)
|
||||||
|
@ -77,7 +79,7 @@ def bar(a, b, c):
|
||||||
pyobject_vectorcall(baz, (a, b, c), None)
|
pyobject_vectorcall(baz, (a, b, c), None)
|
||||||
|
|
||||||
def baz(*args):
|
def baz(*args):
|
||||||
id(42)
|
_idfunc(42)
|
||||||
|
|
||||||
foo(1, 2, 3)
|
foo(1, 2, 3)
|
||||||
|
|
||||||
|
@ -94,7 +96,7 @@ class StackNavigationTests(DebuggerTests):
|
||||||
cmds_after_breakpoint=['py-up', 'py-up'])
|
cmds_after_breakpoint=['py-up', 'py-up'])
|
||||||
self.assertMultilineMatches(bt,
|
self.assertMultilineMatches(bt,
|
||||||
r'''^.*
|
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]+ <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'])
|
cmds_after_breakpoint=['py-up', 'py-up', 'py-down'])
|
||||||
self.assertMultilineMatches(bt,
|
self.assertMultilineMatches(bt,
|
||||||
r'''^.*
|
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]+ <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):
|
class PyPrintTests(DebuggerTests):
|
||||||
|
|
|
@ -17,7 +17,7 @@ class PrettyPrintTests(DebuggerTests):
|
||||||
import_site=False):
|
import_site=False):
|
||||||
# Given an input python source representation of data,
|
# Given an input python source representation of data,
|
||||||
# run "python -c'id(DATA)'" under gdb with a breakpoint on
|
# 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
|
# parameter, and verify that the gdb displays the same string
|
||||||
#
|
#
|
||||||
# Verify that the gdb displays the expected string
|
# Verify that the gdb displays the expected string
|
||||||
|
@ -29,6 +29,7 @@ class PrettyPrintTests(DebuggerTests):
|
||||||
# undecodable characters may lurk there in optimized mode
|
# undecodable characters may lurk there in optimized mode
|
||||||
# (issue #19743).
|
# (issue #19743).
|
||||||
cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"]
|
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,
|
gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
|
||||||
cmds_after_breakpoint=cmds_after_breakpoint,
|
cmds_after_breakpoint=cmds_after_breakpoint,
|
||||||
import_site=import_site)
|
import_site=import_site)
|
||||||
|
@ -36,10 +37,10 @@ class PrettyPrintTests(DebuggerTests):
|
||||||
# in its output, depending on the width of the terminal it's connected
|
# in its output, depending on the width of the terminal it's connected
|
||||||
# to (using its "wrap_here" function)
|
# to (using its "wrap_here" function)
|
||||||
m = re.search(
|
m = re.search(
|
||||||
# Match '#0 builtin_id(self=..., v=...)'
|
# Match '#0 _typing_idfunc(module=..., x=...)'
|
||||||
r'#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)?\)'
|
r'#0\s+_typing__idfunc\s+\(module\=.*,\s+x=\s*(.*?)?\)'
|
||||||
# Match ' at Python/bltinmodule.c'.
|
# 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
|
# but accept any "Directory\file.c" to support Link Time
|
||||||
# Optimization (LTO).
|
# Optimization (LTO).
|
||||||
r'\s+at\s+\S*[A-Za-z]+/[A-Za-z0-9_-]+\.c',
|
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
|
return m.group(1), gdb_output
|
||||||
|
|
||||||
def test_getting_backtrace(self):
|
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)
|
self.assertTrue(BREAKPOINT_FN in gdb_output)
|
||||||
|
|
||||||
def assertGdbRepr(self, val, exp_repr=None):
|
def assertGdbRepr(self, val, exp_repr=None):
|
||||||
# Ensure that gdb's rendering of the value in a debugged process
|
# Ensure that gdb's rendering of the value in a debugged process
|
||||||
# matches repr(value) in this 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:
|
if not exp_repr:
|
||||||
exp_repr = repr(val)
|
exp_repr = repr(val)
|
||||||
self.assertEqual(gdb_repr, exp_repr,
|
self.assertEqual(gdb_repr, exp_repr,
|
||||||
|
@ -173,7 +174,7 @@ class PrettyPrintTests(DebuggerTests):
|
||||||
# which happens on deletion:
|
# which happens on deletion:
|
||||||
gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
|
gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
|
||||||
s.remove('a')
|
s.remove('a')
|
||||||
id(s)''')
|
_idfunc(s)''')
|
||||||
self.assertEqual(gdb_repr, "{'b'}")
|
self.assertEqual(gdb_repr, "{'b'}")
|
||||||
|
|
||||||
@support.requires_resource('cpu')
|
@support.requires_resource('cpu')
|
||||||
|
@ -194,7 +195,7 @@ id(s)''')
|
||||||
try:
|
try:
|
||||||
raise RuntimeError("I am an error")
|
raise RuntimeError("I am an error")
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
id(e)
|
_idfunc(e)
|
||||||
''')
|
''')
|
||||||
self.assertEqual(gdb_repr,
|
self.assertEqual(gdb_repr,
|
||||||
"RuntimeError('I am an error',)")
|
"RuntimeError('I am an error',)")
|
||||||
|
@ -205,7 +206,7 @@ except RuntimeError as e:
|
||||||
try:
|
try:
|
||||||
a = 1 / 0
|
a = 1 / 0
|
||||||
except ZeroDivisionError as e:
|
except ZeroDivisionError as e:
|
||||||
id(e)
|
_idfunc(e)
|
||||||
''')
|
''')
|
||||||
self.assertEqual(gdb_repr,
|
self.assertEqual(gdb_repr,
|
||||||
"ZeroDivisionError('division by zero',)")
|
"ZeroDivisionError('division by zero',)")
|
||||||
|
@ -217,7 +218,7 @@ class Foo:
|
||||||
pass
|
pass
|
||||||
foo = Foo()
|
foo = Foo()
|
||||||
foo.an_int = 42
|
foo.an_int = 42
|
||||||
id(foo)''')
|
_idfunc(foo)''')
|
||||||
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
||||||
self.assertTrue(m,
|
self.assertTrue(m,
|
||||||
msg='Unexpected new-style class rendering %r' % gdb_repr)
|
msg='Unexpected new-style class rendering %r' % gdb_repr)
|
||||||
|
@ -230,7 +231,7 @@ class Foo(list):
|
||||||
foo = Foo()
|
foo = Foo()
|
||||||
foo += [1, 2, 3]
|
foo += [1, 2, 3]
|
||||||
foo.an_int = 42
|
foo.an_int = 42
|
||||||
id(foo)''')
|
_idfunc(foo)''')
|
||||||
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
||||||
|
|
||||||
self.assertTrue(m,
|
self.assertTrue(m,
|
||||||
|
@ -245,7 +246,7 @@ class Foo(tuple):
|
||||||
pass
|
pass
|
||||||
foo = Foo((1, 2, 3))
|
foo = Foo((1, 2, 3))
|
||||||
foo.an_int = 42
|
foo.an_int = 42
|
||||||
id(foo)''')
|
_idfunc(foo)''')
|
||||||
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
||||||
|
|
||||||
self.assertTrue(m,
|
self.assertTrue(m,
|
||||||
|
@ -283,8 +284,8 @@ id(foo)''')
|
||||||
def test_NULL_ptr(self):
|
def test_NULL_ptr(self):
|
||||||
'Ensure that a NULL PyObject* is handled gracefully'
|
'Ensure that a NULL PyObject* is handled gracefully'
|
||||||
gdb_repr, gdb_output = (
|
gdb_repr, gdb_output = (
|
||||||
self.get_gdb_repr('id(42)',
|
self.get_gdb_repr('_idfunc(42)',
|
||||||
cmds_after_breakpoint=['set variable v=0',
|
cmds_after_breakpoint=['set variable x=0',
|
||||||
'backtrace'])
|
'backtrace'])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -292,25 +293,25 @@ id(foo)''')
|
||||||
|
|
||||||
def test_NULL_ob_type(self):
|
def test_NULL_ob_type(self):
|
||||||
'Ensure that a PyObject* with NULL ob_type is handled gracefully'
|
'Ensure that a PyObject* with NULL ob_type is handled gracefully'
|
||||||
self.assertSane('id(42)',
|
self.assertSane('_idfunc(42)',
|
||||||
'set v->ob_type=0')
|
'set x->ob_type=0')
|
||||||
|
|
||||||
def test_corrupt_ob_type(self):
|
def test_corrupt_ob_type(self):
|
||||||
'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
|
'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
|
||||||
self.assertSane('id(42)',
|
self.assertSane('_idfunc(42)',
|
||||||
'set v->ob_type=0xDEADBEEF',
|
'set x->ob_type=0xDEADBEEF',
|
||||||
exprepr='42')
|
exprepr='42')
|
||||||
|
|
||||||
def test_corrupt_tp_flags(self):
|
def test_corrupt_tp_flags(self):
|
||||||
'Ensure that a PyObject* with a type with corrupt tp_flags is handled'
|
'Ensure that a PyObject* with a type with corrupt tp_flags is handled'
|
||||||
self.assertSane('id(42)',
|
self.assertSane('_idfunc(42)',
|
||||||
'set v->ob_type->tp_flags=0x0',
|
'set x->ob_type->tp_flags=0x0',
|
||||||
exprepr='42')
|
exprepr='42')
|
||||||
|
|
||||||
def test_corrupt_tp_name(self):
|
def test_corrupt_tp_name(self):
|
||||||
'Ensure that a PyObject* with a type with corrupt tp_name is handled'
|
'Ensure that a PyObject* with a type with corrupt tp_name is handled'
|
||||||
self.assertSane('id(42)',
|
self.assertSane('_idfunc(42)',
|
||||||
'set v->ob_type->tp_name=0xDEADBEEF',
|
'set x->ob_type->tp_name=0xDEADBEEF',
|
||||||
exprepr='42')
|
exprepr='42')
|
||||||
|
|
||||||
def test_builtins_help(self):
|
def test_builtins_help(self):
|
||||||
|
@ -321,7 +322,7 @@ id(foo)''')
|
||||||
|
|
||||||
# (this was the issue causing tracebacks in
|
# (this was the issue causing tracebacks in
|
||||||
# http://bugs.python.org/issue8032#msg100537 )
|
# 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)
|
m = re.match(r'<_Helper\(\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
||||||
self.assertTrue(m,
|
self.assertTrue(m,
|
||||||
|
@ -331,18 +332,18 @@ id(foo)''')
|
||||||
'''Ensure that a reference loop involving a list doesn't lead proxyval
|
'''Ensure that a reference loop involving a list doesn't lead proxyval
|
||||||
into an infinite loop:'''
|
into an infinite loop:'''
|
||||||
gdb_repr, gdb_output = \
|
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, [...]]')
|
self.assertEqual(gdb_repr, '[3, 4, 5, [...]]')
|
||||||
|
|
||||||
gdb_repr, gdb_output = \
|
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, [[...]]]')
|
self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]')
|
||||||
|
|
||||||
def test_selfreferential_dict(self):
|
def test_selfreferential_dict(self):
|
||||||
'''Ensure that a reference loop involving a dict doesn't lead proxyval
|
'''Ensure that a reference loop involving a dict doesn't lead proxyval
|
||||||
into an infinite loop:'''
|
into an infinite loop:'''
|
||||||
gdb_repr, gdb_output = \
|
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': {...}}}")
|
self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}")
|
||||||
|
|
||||||
|
@ -353,7 +354,7 @@ class Foo:
|
||||||
pass
|
pass
|
||||||
foo = Foo()
|
foo = Foo()
|
||||||
foo.an_attr = foo
|
foo.an_attr = foo
|
||||||
id(foo)''')
|
_idfunc(foo)''')
|
||||||
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
|
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
|
||||||
gdb_repr),
|
gdb_repr),
|
||||||
'Unexpected gdb representation: %r\n%s' % \
|
'Unexpected gdb representation: %r\n%s' % \
|
||||||
|
@ -366,7 +367,7 @@ class Foo(object):
|
||||||
pass
|
pass
|
||||||
foo = Foo()
|
foo = Foo()
|
||||||
foo.an_attr = foo
|
foo.an_attr = foo
|
||||||
id(foo)''')
|
_idfunc(foo)''')
|
||||||
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
|
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
|
||||||
gdb_repr),
|
gdb_repr),
|
||||||
'Unexpected gdb representation: %r\n%s' % \
|
'Unexpected gdb representation: %r\n%s' % \
|
||||||
|
@ -380,7 +381,7 @@ a = Foo()
|
||||||
b = Foo()
|
b = Foo()
|
||||||
a.an_attr = b
|
a.an_attr = b
|
||||||
b.an_attr = a
|
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]+>',
|
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),
|
gdb_repr),
|
||||||
'Unexpected gdb representation: %r\n%s' % \
|
'Unexpected gdb representation: %r\n%s' % \
|
||||||
|
@ -388,7 +389,7 @@ id(a)''')
|
||||||
|
|
||||||
def test_truncation(self):
|
def test_truncation(self):
|
||||||
'Verify that very long output is truncated'
|
'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,
|
self.assertEqual(gdb_repr,
|
||||||
"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, "
|
"[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, "
|
"14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, "
|
||||||
|
@ -415,7 +416,7 @@ id(a)''')
|
||||||
1024 + len('...(truncated)'))
|
1024 + len('...(truncated)'))
|
||||||
|
|
||||||
def test_builtin_method(self):
|
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]+>',
|
self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>',
|
||||||
gdb_repr),
|
gdb_repr),
|
||||||
'Unexpected gdb representation: %r\n%s' % \
|
'Unexpected gdb representation: %r\n%s' % \
|
||||||
|
@ -424,15 +425,16 @@ id(a)''')
|
||||||
def test_frames(self):
|
def test_frames(self):
|
||||||
gdb_output = self.get_stack_trace('''
|
gdb_output = self.get_stack_trace('''
|
||||||
import sys
|
import sys
|
||||||
|
from _typing import _idfunc
|
||||||
def foo(a, b, c):
|
def foo(a, b, c):
|
||||||
return sys._getframe(0)
|
return sys._getframe(0)
|
||||||
|
|
||||||
f = foo(3, 4, 5)
|
f = foo(3, 4, 5)
|
||||||
id(f)''',
|
_idfunc(f)''',
|
||||||
breakpoint='builtin_id',
|
breakpoint='_typing__idfunc',
|
||||||
cmds_after_breakpoint=['print (PyFrameObject*)v']
|
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,
|
gdb_output,
|
||||||
re.DOTALL),
|
re.DOTALL),
|
||||||
'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))
|
'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))
|
||||||
|
|
|
@ -16,7 +16,7 @@ CHECKOUT_HOOK_PATH = os.path.join(os.path.dirname(sys.executable),
|
||||||
'python-gdb.py')
|
'python-gdb.py')
|
||||||
|
|
||||||
SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), 'gdb_sample.py')
|
SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), 'gdb_sample.py')
|
||||||
BREAKPOINT_FN = 'builtin_id'
|
BREAKPOINT_FN = '_typing__idfunc'
|
||||||
|
|
||||||
PYTHONHASHSEED = '123'
|
PYTHONHASHSEED = '123'
|
||||||
|
|
||||||
|
|
|
@ -4558,11 +4558,11 @@ class MiscIOTest(unittest.TestCase):
|
||||||
''')
|
''')
|
||||||
proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code)
|
proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code)
|
||||||
warnings = proc.err.splitlines()
|
warnings = proc.err.splitlines()
|
||||||
self.assertEqual(len(warnings), 4)
|
self.assertEqual(len(warnings), 2)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
warnings[0].startswith(b"<string>:5: EncodingWarning: "))
|
warnings[0].startswith(b"<string>:5: EncodingWarning: "))
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
warnings[2].startswith(b"<string>:8: EncodingWarning: "))
|
warnings[1].startswith(b"<string>:8: EncodingWarning: "))
|
||||||
|
|
||||||
def test_text_encoding(self):
|
def test_text_encoding(self):
|
||||||
# PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8"
|
# PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8"
|
||||||
|
|
|
@ -8,6 +8,7 @@ import tokenize
|
||||||
from importlib.machinery import ModuleSpec
|
from importlib.machinery import ModuleSpec
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import os_helper
|
from test.support import os_helper
|
||||||
|
from test.support.script_helper import assert_python_ok
|
||||||
|
|
||||||
|
|
||||||
FILENAME = linecache.__file__
|
FILENAME = linecache.__file__
|
||||||
|
@ -311,6 +312,12 @@ class LineCacheTests(unittest.TestCase):
|
||||||
# just to be sure that we did not mess with cache
|
# just to be sure that we did not mess with cache
|
||||||
linecache.clearcache()
|
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):
|
class LineCacheInvalidationTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -213,7 +213,7 @@ class TestInteractiveInterpreter(unittest.TestCase):
|
||||||
p.stdin.write(user_input)
|
p.stdin.write(user_input)
|
||||||
user_input2 = dedent("""
|
user_input2 = dedent("""
|
||||||
import linecache
|
import linecache
|
||||||
print(linecache.cache['<stdin>-1'])
|
print(linecache._interactive_cache[id(foo.__code__)])
|
||||||
""")
|
""")
|
||||||
p.stdin.write(user_input2)
|
p.stdin.write(user_input2)
|
||||||
output = kill_python(p)
|
output = kill_python(p)
|
||||||
|
|
|
@ -1804,9 +1804,9 @@ class RunFuncTestCase(BaseTestCase):
|
||||||
cp = subprocess.run([sys.executable, "-Xwarn_default_encoding", "-c", code],
|
cp = subprocess.run([sys.executable, "-Xwarn_default_encoding", "-c", code],
|
||||||
capture_output=True)
|
capture_output=True)
|
||||||
lines = cp.stderr.splitlines()
|
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[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():
|
def _get_test_grp_name():
|
||||||
|
|
|
@ -288,11 +288,11 @@ class FrameSummary:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno',
|
__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,
|
def __init__(self, filename, lineno, name, *, lookup_line=True,
|
||||||
locals=None, line=None,
|
locals=None, line=None,
|
||||||
end_lineno=None, colno=None, end_colno=None):
|
end_lineno=None, colno=None, end_colno=None, **kwargs):
|
||||||
"""Construct a FrameSummary.
|
"""Construct a FrameSummary.
|
||||||
|
|
||||||
:param lookup_line: If True, `linecache` is consulted for the source
|
:param lookup_line: If True, `linecache` is consulted for the source
|
||||||
|
@ -308,6 +308,7 @@ class FrameSummary:
|
||||||
self.colno = colno
|
self.colno = colno
|
||||||
self.end_colno = end_colno
|
self.end_colno = end_colno
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self._code = kwargs.get("_code")
|
||||||
self._lines = line
|
self._lines = line
|
||||||
self._lines_dedented = None
|
self._lines_dedented = None
|
||||||
if lookup_line:
|
if lookup_line:
|
||||||
|
@ -347,7 +348,10 @@ class FrameSummary:
|
||||||
lines = []
|
lines = []
|
||||||
for lineno in range(self.lineno, self.end_lineno + 1):
|
for lineno in range(self.lineno, self.end_lineno + 1):
|
||||||
# treat errors (empty string) and empty lines (newline) as the same
|
# 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"
|
self._lines = "\n".join(lines) + "\n"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -484,9 +488,13 @@ class StackSummary(list):
|
||||||
f_locals = f.f_locals
|
f_locals = f.f_locals
|
||||||
else:
|
else:
|
||||||
f_locals = None
|
f_locals = None
|
||||||
result.append(FrameSummary(
|
result.append(
|
||||||
filename, lineno, name, lookup_line=False, locals=f_locals,
|
FrameSummary(filename, lineno, name,
|
||||||
end_lineno=end_lineno, colno=colno, end_colno=end_colno))
|
lookup_line=False, locals=f_locals,
|
||||||
|
end_lineno=end_lineno, colno=colno, end_colno=end_colno,
|
||||||
|
_code=f.f_code,
|
||||||
|
)
|
||||||
|
)
|
||||||
for filename in fnames:
|
for filename in fnames:
|
||||||
linecache.checkcache(filename)
|
linecache.checkcache(filename)
|
||||||
|
|
||||||
|
|
|
@ -1410,7 +1410,7 @@ run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
|
||||||
|
|
||||||
PyObject* result = PyObject_CallFunction(
|
PyObject* result = PyObject_CallFunction(
|
||||||
print_tb_func, "OOO",
|
print_tb_func, "OOO",
|
||||||
interactive_filename,
|
co,
|
||||||
interactive_src,
|
interactive_src,
|
||||||
filename
|
filename
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue