Skip to content

Commit 43254f0

Browse files
committed
Do better with exec/eval strings
1 parent 0b1a296 commit 43254f0

File tree

2 files changed

+70
-25
lines changed

2 files changed

+70
-25
lines changed

trepan/lib/stack.py

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
# Copyright (C) 2008-2010, 2013, 2015, 2017-2018, 2020-2021,
4-
# 2023-2025 Rocky Bernstein <rocky@gnu.org>
4+
# 2023-2026 Rocky Bernstein <rocky@gnu.org>
55
#
66
# This program is free software: you can redistribute it and/or modify
77
# it under the terms of the GNU General Public License as published by
@@ -15,7 +15,7 @@
1515
#
1616
# You should have received a copy of the GNU General Public License
1717
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18-
""" Functions for working with Python frames"""
18+
"""Functions for working with Python frames"""
1919

2020
import dis
2121
import inspect
@@ -27,9 +27,7 @@
2727
from reprlib import repr
2828
from types import CodeType, FrameType
2929
from typing import Optional, Tuple
30-
3130
import xdis
32-
from xdis import get_opcode
3331
from xdis.version_info import PYTHON_IMPLEMENTATION, PYTHON_VERSION_TRIPLE
3432

3533
from trepan.lib.bytecode import op_at_frame
@@ -48,8 +46,11 @@
4846
try:
4947
from trepan.processor.cmdfns import deparse_fn
5048
except ImportError:
49+
5150
def deparse_fn(code):
5251
raise NotImplementedError
52+
53+
5354
try:
5455
from trepan.lib.deparse import deparse_offset
5556

@@ -63,6 +64,8 @@ def deparse_offset(_code, _name: str, _list_i: int, _) -> tuple:
6364

6465
_with_local_varname = re.compile(r"_\[[0-9+]]")
6566

67+
opc = xdis.get_opcode_module(PYTHON_VERSION_TRIPLE, PYTHON_IMPLEMENTATION)
68+
6669

6770
def count_frames(frame: FrameType, count_start=0) -> int:
6871
"""Return a count of the number of frames"""
@@ -77,6 +80,7 @@ def count_frames(frame: FrameType, count_start=0) -> int:
7780
return 1000
7881
return count
7982

83+
8084
def get_column_start_from_frame(frame: FrameType) -> int:
8185
"""
8286
Given a code frame, return the start column for that
@@ -85,6 +89,7 @@ def get_column_start_from_frame(frame: FrameType) -> int:
8589
"""
8690
return get_column_start_from_code(frame.f_code, frame.f_lasti)
8791

92+
8893
def get_column_start_from_code(code: CodeType, f_lasti: int) -> int:
8994
"""
9095
Given a code object, return the start column for that
@@ -100,6 +105,7 @@ def get_column_start_from_code(code: CodeType, f_lasti: int) -> int:
100105
return position_tuple[2]
101106
return -1
102107

108+
103109
_re_pseudo_file = re.compile(r"^<.+>")
104110

105111

@@ -147,12 +153,14 @@ def deparse_source_from_code(code):
147153
return source_text
148154

149155

150-
def format_function_name(frame: FrameType, style: str) -> Tuple[Optional[str], Optional[str]]:
156+
def format_function_name(
157+
frame: FrameType, style: str
158+
) -> Tuple[Optional[str], Optional[str]]:
151159
"""
152160
Pick out the function name from ``frame`` and return both the name
153161
and the name styled according to ``style``
154162
"""
155-
if (exec_type := is_eval_or_exec_stmt(frame)):
163+
if exec_type := is_eval_or_exec_stmt(frame):
156164
funcname = get_call_function_name(frame)
157165
if funcname is None:
158166
funcname = exec_type
@@ -167,7 +175,9 @@ def format_function_name(frame: FrameType, style: str) -> Tuple[Optional[str], O
167175
return funcname, format_token(Function, funcname, style=style)
168176

169177

170-
def format_function_and_parameters(frame: FrameType, debugger, style: str) -> Tuple[bool, str]:
178+
def format_function_and_parameters(
179+
frame: FrameType, debugger, style: str
180+
) -> Tuple[bool, str]:
171181
""" """
172182

173183
funcname, s = format_function_name(frame, style)
@@ -248,12 +258,12 @@ def format_return_and_location(
248258
elif s == "?()":
249259
if is_eval_or_exec_stmt(frame):
250260
s = "in exec"
251-
# exec_str = get_exec_string(frame.f_back)
252-
# if exec_str != None:
253-
# filename = exec_str
254-
# add_quotes_around_file = False
255-
# pass
256-
# pass
261+
exec_str = get_exec_string(frame.f_back)
262+
if exec_str is not None:
263+
filename = exec_str
264+
add_quotes_around_file = False
265+
pass
266+
pass
257267
elif not is_pseudo_file:
258268
s = "in file"
259269
pass
@@ -336,6 +346,25 @@ def frame2filesize(frame):
336346
return None, None
337347

338348

349+
def get_exec_string(frame: FrameType) -> Optional[str]:
350+
if (call_frame := frame.f_back) is not None:
351+
offset = call_frame.f_lasti - 2
352+
code = call_frame.f_code
353+
while offset > 0:
354+
inst = list(xdis.bytecode.get_logical_instruction_at_offset(
355+
code.co_code, offset, opc, constants=code.co_consts
356+
))[0]
357+
if inst.opname in ("PRECALL", "CACHE"):
358+
pass
359+
elif inst.opname == "LOAD_CONST":
360+
return inst.argval
361+
else:
362+
break
363+
offset -= 2
364+
365+
return None
366+
367+
339368
def check_path_with_frame(frame, path):
340369
my_size = os.stat(path).st_size
341370
fs_size, bc_size = frame2filesize(frame)
@@ -365,9 +394,6 @@ def is_eval_or_exec_stmt(frame) -> Optional[str]:
365394
return None
366395

367396

368-
opc = get_opcode(PYTHON_VERSION_TRIPLE, PYTHON_IMPLEMENTATION)
369-
370-
371397
def get_call_function_name(frame) -> Optional[str]:
372398
"""If f_back is looking at a call function, return
373399
the name for it. Otherwise, return None"""
@@ -575,6 +601,7 @@ def __init__(self):
575601
# print(pyc_file, getsourcefile(pyc_file))
576602

577603
from trepan.debugger import Trepan
604+
578605
m = MockDebugger()
579606

580607
# For testing print_stack_entry()
@@ -597,6 +624,7 @@ def __init__(self):
597624
)
598625
)
599626
import sys
627+
600628
sys.exit(0)
601629
# print("frame count: %d" % count_frames(frame))
602630
# print("frame count: %d" % count_frames(frame.f_back))
@@ -613,7 +641,11 @@ def fn(x):
613641
eval_str = is_eval_or_exec_stmt(frame.f_back)
614642
if eval_str:
615643
print(f"Caller is {eval_str} stmt")
616-
print(format_stack_entry(dd, (frame.f_back, frame.f_back.f_code.co_firstlineno)))
644+
print(
645+
format_stack_entry(
646+
dd, (frame.f_back, frame.f_back.f_code.co_firstlineno)
647+
)
648+
)
617649

618650
_, mess = format_function_and_parameters(frame, dd, style="tango")
619651
print(mess)

trepan/processor/print.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
#
3-
# Copyright (C) 2024-2025 Rocky Bernstein <rocky@gnu.org>
3+
# Copyright (C) 2024-2026 Rocky Bernstein <rocky@gnu.org>
44
#
55
# This program is free software: you can redistribute it and/or modify
66
# it under the terms of the GNU General Public License as published by
@@ -26,7 +26,7 @@
2626
import pyficache
2727

2828
from trepan.lib.format import Filename, Hex, LineNumber, Symbol, format_token # Opcode,
29-
from trepan.lib.stack import check_path_with_frame, frame2file, is_eval_or_exec_stmt
29+
from trepan.lib.stack import check_path_with_frame, frame2file, get_exec_string, is_eval_or_exec_stmt
3030
from trepan.processor import cmdfns
3131
from trepan.processor.cmdfns import deparse_fn
3232

@@ -140,8 +140,10 @@ def prefix_for_filename(deparsed_text: str) -> str:
140140
lines = deparsed_text.split("\n")
141141
# FIXME Rather than blindly take the first line,
142142
# check if it is blank and if so use other lines.
143-
first_text_line = lines[0]
144-
return proc_obj._saferepr(first_text_line)[1:-1][:10]
143+
for line in lines:
144+
if line:
145+
return proc_obj._saferepr(line.strip())[1:-1][:10]
146+
return "..."
145147

146148
i_stack = proc_obj.curindex
147149
if i_stack is None or proc_obj.stack is None:
@@ -217,8 +219,12 @@ def prefix_for_filename(deparsed_text: str) -> str:
217219
# else:
218220
# print("Can't deparse", frame.f_code)
219221
if source_text is None and eval_kind:
220-
source_text = f"{eval_kind}(...)"
221-
pass
222+
if (source_text := get_exec_string(frame)):
223+
filename = "string-" + prefix_for_filename(source_text) + "-"
224+
else:
225+
source_text = f"{eval_kind}(...)"
226+
pass
227+
pass
222228
pass
223229
pass
224230
else:
@@ -283,7 +289,7 @@ def prefix_for_filename(deparsed_text: str) -> str:
283289
# FIXME:
284290
if source_text:
285291
lines = source_text.split("\n")
286-
temp_name = "string-" + prefix_for_filename(source_text)
292+
temp_name = "string-" + prefix_for_filename(source_text) + "-"
287293
else:
288294
# try with good ol linecache and consider fixing pyficache
289295
lines = linecache.getlines(filename)
@@ -298,7 +304,7 @@ def prefix_for_filename(deparsed_text: str) -> str:
298304
dir=proc_obj.settings("tempdir"),
299305
)
300306
with fd:
301-
fd.write("".join(lines).encode("utf-8"))
307+
fd.write("\n".join(lines).encode("utf-8"))
302308
remapped_file = fd.name
303309
pyficache.remap_file(remapped_file, filename)
304310
fd.close()
@@ -397,8 +403,15 @@ def five():
397403
cmdproc.event = "line"
398404
cmdproc.setup()
399405
print_location(cmdproc)
406+
400407
cmdproc.curindex = 1
401408
cmdproc.curframe = cmdproc.stack[cmdproc.curindex][0]
402409
print_location(cmdproc)
403410

411+
exec("""
412+
cmdproc.frame = currentframe()
413+
cmdproc.setup()
414+
print_location(cmdproc)
415+
""")
416+
404417
exec("five()")

0 commit comments

Comments
 (0)