Skip to content

Commit ea9c239

Browse files
committed
Improve handling of frozen modules...
DRY tempfile creation in print. Make a pass over "info line"
1 parent b2c2d4c commit ea9c239

File tree

5 files changed

+108
-75
lines changed

5 files changed

+108
-75
lines changed

trepan/bwprocessor/location.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def format_location(proc_obj):
3838
location["lineno"] = lineno
3939

4040
if "<string>" == filename and dbgr_obj.eval_string:
41+
# DRY with create_tempfile_and_remap_filename in trepan.processor.print.
4142
filename = pyficache.unmap_file(filename)
4243
if "<string>" == filename:
4344
fd = tempfile.NamedTemporaryFile(

trepan/lib/stack.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
from trepan.lib.format import (
3737
Arrow,
3838
Filename,
39-
Function,
4039
LineNumber,
4140
Return,
4241
format_function,

trepan/processor/cmdfns.py

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
#
3-
# Copyright (C) 2013, 2015, 2017-2018, 2020-2021, 2023-2024 Rocky
3+
# Copyright (C) 2013, 2015, 2017-2018, 2020-2021, 2023-2024, 2026 Rocky
44
# Bernstein <[email protected]>
55
#
66
# This program is free software: you can redistribute it and/or modify
@@ -20,43 +20,10 @@
2020
on/off setting value.
2121
"""
2222
import sys
23-
import tempfile
24-
25-
from xdis import IS_PYPY, PYTHON_VERSION_TRIPLE
26-
27-
28-
def source_tempfile_remap(prefix, text, tempdir=None):
29-
fd = tempfile.NamedTemporaryFile(
30-
suffix=".py", prefix=prefix, dir=tempdir, delete=False
31-
)
32-
with fd:
33-
fd.write(bytes(text, "UTF-8"))
34-
fd.close()
35-
pass
36-
return fd.name
3723

3824

3925
def deparse_fn(code):
40-
try:
41-
if PYTHON_VERSION_TRIPLE >= (3, 9):
42-
# Don't have decompiler here yet.
43-
return None
44-
if (3, 7) <= PYTHON_VERSION_TRIPLE < (3, 9):
45-
from decompyle3.semantics.linemap import (
46-
code_deparse_with_fragments_and_map as deparse_code,
47-
)
48-
else:
49-
from uncompyle6.semantics.linemap import (
50-
code_deparse_with_fragments_and_map as deparse_code,
51-
)
52-
53-
except ImportError:
54-
return None
55-
try:
56-
deparsed = deparse_code(code, is_pypy=IS_PYPY)
57-
return deparsed
58-
except Exception:
59-
raise
26+
# Don't have decompiler here yet.
6027
return None
6128

6229

trepan/processor/command/info_subcmd/line.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import re
2121

2222
import columnize
23-
from pyficache import get_linecache_info
23+
from pyficache import get_linecache_info, file2file_remap
2424

2525
from trepan.clifns import search_file
2626
from trepan.lib.format import Filename, format_line_number, format_token
@@ -93,6 +93,7 @@ def lineinfo(self, arg):
9393
def run(self, args):
9494
"""Current line number in source file"""
9595
# info line <loc>
96+
remapped_filename = None
9697
if len(args) == 0:
9798
if not self.proc.curframe:
9899
self.errmsg("Frame is needed when no line number is given.")
@@ -101,7 +102,6 @@ def run(self, args):
101102
# No line number. Use current frame line number
102103
line_number = inspect.getlineno(self.proc.curframe)
103104
filename = self.core.canonic_filename(self.proc.curframe)
104-
105105
elif len(args) == 1:
106106
# lineinfo returns (item, file, lineno) or (None,)
107107
line_number, filename = self.lineinfo(args[2:])
@@ -112,24 +112,30 @@ def run(self, args):
112112
else:
113113
self.errmsg("Wrong number of arguments.")
114114
return
115-
if not osp.isfile(filename):
116-
filename = search_file(filename, self.core.search_path, self.main_dirname)
117-
pass
118115

119-
# FIXME: this needs work.
120-
linecache_info = get_linecache_info(filename)
121-
if line_number not in linecache_info.line_numbers:
122-
self.errmsg(
123-
"No line information for line %d of %s" % (line_number, filename)
124-
)
125-
return
116+
remapped_filename = file2file_remap.get(filename, filename)
126117

127118
style = self.settings["style"]
128119
formatted_filename = format_token(
129120
Filename,
130-
format_token(Filename, self.core.filename(filename), style=style),
121+
format_token(Filename, remapped_filename, style=style),
131122
style=style,
132123
)
124+
125+
if remapped_filename != filename:
126+
self.msg(f"{filename} remapped to {formatted_filename}")
127+
128+
if not osp.isfile(remapped_filename):
129+
filename = search_file(remapped_filename, self.core.search_path, self.main_dirname)
130+
pass
131+
132+
linecache_info = get_linecache_info(remapped_filename)
133+
if line_number not in linecache_info.line_numbers:
134+
self.errmsg(
135+
"No line information for line %d of %s" % (line_number, formatted_filename)
136+
)
137+
return
138+
133139
formatted_line_number = format_line_number(line_number, style)
134140
msg1 = "Line %s of %s" % (formatted_line_number, formatted_filename)
135141
line_info = linecache_info.line_info
@@ -152,7 +158,7 @@ def run(self, args):
152158
else:
153159
self.errmsg(
154160
"No line information for line %d of %s"
155-
% (line_number, self.core.filename(filename))
161+
% (line_number, formatted_filename)
156162
)
157163
return False
158164

trepan/processor/print.py

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from inspect import currentframe, ismodule
2323
from tempfile import NamedTemporaryFile
2424
from types import CodeType
25+
from typing import Optional
2526

2627
import pyficache
2728

@@ -104,8 +105,8 @@ def print_source_location_info(
104105
fn_name=None,
105106
f_lasti=None,
106107
remapped_file=None,
107-
remapped_line_number: int=-1,
108-
remapped_column_number: int=-1,
108+
remapped_line_number: int = -1,
109+
remapped_column_number: int = -1,
109110
):
110111
"""Print out a source location, e.g. the first line in
111112
line in:
@@ -154,7 +155,7 @@ def prefix_for_filename(deparsed_text: str) -> str:
154155
# check if it is blank and if so use other lines.
155156
for line in lines:
156157
if line:
157-
cleaned_line = re.sub(r"[()'\"\s]", "_", line.strip())
158+
cleaned_line = re.sub(r"[()<> '\"\s]", "_", line.strip())
158159
return proc_obj._saferepr(cleaned_line)[1:-1][:10]
159160
return "..."
160161

@@ -221,10 +222,12 @@ def prefix_for_source_text(source_text: str, maxwidth: int) -> str:
221222
if not (
222223
remapped_file := pyficache.main.code2tempfile.get(frame.f_code)
223224
):
224-
remapped_file = cmdfns.source_tempfile_remap(
225-
"eval_string",
225+
remapped_file = create_tempfile_and_remap_filename(
226226
dbgr_obj.eval_string,
227227
tempdir=proc_obj.settings("tempdir"),
228+
prefix="eval_string",
229+
suffix=".py",
230+
delete=False,
228231
)
229232
# pyficache.remap_file(filename, remapped_file)
230233
pyficache.main.code2tempfile[frame.f_code] = filename
@@ -270,8 +273,8 @@ def prefix_for_source_text(source_text: str, maxwidth: int) -> str:
270273
pass
271274
else:
272275
m = re.search("^<frozen (.*)>", filename)
273-
if m and m.group(1) in pyficache.file2file_remap:
274-
remapped_file = pyficache.file2file_remap[m.group(1)]
276+
if m and (frozen_name := m.group(1)) in pyficache.file2file_remap:
277+
remapped_file = pyficache.file2file_remap[frozen_name]
275278
pass
276279
elif filename in pyficache.file2file_remap:
277280
remapped_file = pyficache.file2file_remap[filename]
@@ -281,10 +284,12 @@ def prefix_for_source_text(source_text: str, maxwidth: int) -> str:
281284
remapped_file = pyficache.remap_file_pat(
282285
filename, pyficache.main.remap_re_hash
283286
)
284-
elif m and m.group(1) in sys.modules:
285-
remapped_file = m.group(1)
286-
pyficache.remap_file(filename, remapped_file)
287-
pass
287+
elif m and frozen_name in sys.modules:
288+
# try with good ol linecache and consider fixing pyficache
289+
remapped_file = sys.modules[frozen_name].__file__
290+
pyficache.remap_file(remapped_file, filename)
291+
filename = remapped_file
292+
pass
288293

289294
opts = {
290295
"reload_on_change": proc_obj.settings("reload"),
@@ -303,15 +308,18 @@ def prefix_for_source_text(source_text: str, maxwidth: int) -> str:
303308
if orig_line_number == 0:
304309
line_number = 0
305310
line, remapped_line_number = pyficache.get_pyasm_line(
306-
filename, line_number, is_source_line=True, offset=frame.f_lasti,
311+
filename,
312+
line_number,
313+
is_source_line=True,
314+
offset=frame.f_lasti,
307315
)
308316
if remapped_line_number >= 0:
309317
# FIXME: +1 is because getlines is 0 origin.
310318
remapped_line_number += 1
311319
if line is not None and opts["style"] not in ("plain", "none"):
312320
line = pyficache.highlight_string(
313-
line, lexer=pyficache.pyasm_lexer, style=opts["style"]
314-
)
321+
line, lexer=pyficache.pyasm_lexer, style=opts["style"]
322+
)
315323
if line.endswith("\n"):
316324
line = line[:-1]
317325

@@ -349,19 +357,17 @@ def prefix_for_source_text(source_text: str, maxwidth: int) -> str:
349357
lines = linecache.getlines(filename)
350358
temp_name = filename
351359
if lines and not pyficache.is_python_assembly_file(filename):
352-
# FIXME: DRY code with version in cmdproc.py print_location
360+
####
353361
prefix = osp.basename(temp_name).split(".")[0] + "-"
354-
fd = NamedTemporaryFile(
355-
suffix=".py",
356-
prefix=prefix,
357-
delete=False,
358-
dir=proc_obj.settings("tempdir"),
362+
text = "\n".join(lines)
363+
create_tempfile_and_remap_filename(
364+
text,
365+
filename=filename,
366+
tempdir=proc_obj.settings("tempdir"),
367+
prefix=".py",
368+
delete=False
359369
)
360-
with fd:
361-
fd.write("\n".join(lines).encode("utf-8"))
362-
remapped_file = fd.name
363-
pyficache.remap_file(remapped_file, filename)
364-
fd.close()
370+
#####
365371
if source_text:
366372
pyficache.main.code2tempfile[frame.f_code] = remapped_file
367373
intf_obj.msg(
@@ -452,9 +458,59 @@ def prefix_for_source_text(source_text: str, maxwidth: int) -> str:
452458
return True
453459

454460

461+
def create_tempfile_and_remap_filename(
462+
text: str,
463+
filename: str,
464+
tempdir: str,
465+
prefix: Optional[str] = None,
466+
suffix: str = ".py",
467+
delete=False,
468+
) -> str:
469+
"""
470+
Using `lines` associated with `filename`, create a temporary filename
471+
using `prefix` in directory `tempdir`.
472+
473+
`filename is typically the `co_filename` field inside a Python
474+
code object which is bogus. For example it might be <string> or <frozen runpy>
475+
or some valid filename that just does not exist in our environment.
476+
477+
Therefore we create file name in the filesystem an use pyfcache to
478+
remap that 'filename` to newly create filename with the contents given .
479+
"""
480+
481+
# FIXME: DRY code with version in cmdproc.py print_location
482+
fd = NamedTemporaryFile(
483+
suffix=".py",
484+
prefix=prefix,
485+
delete=delete,
486+
dir=tempdir,
487+
)
488+
with fd:
489+
fd.write(bytes(text, "UTF-8"))
490+
remapped_file = fd.name
491+
pyficache.remap_file(remapped_file, filename)
492+
fd.close()
493+
pass
494+
return fd.name
495+
496+
455497
# Demo it
456498
if __name__ == "__main__":
457499

500+
def inside_frozen():
501+
from trepan.processor.cmdproc import CommandProcessor
502+
from trepan.processor.command.mock import MockDebugger
503+
from inspect import currentframe
504+
from trepan.processor.print import print_location
505+
506+
d = MockDebugger()
507+
cmdproc = CommandProcessor(d.core)
508+
cmdproc.frame = currentframe().f_back
509+
cmdproc.event = "call"
510+
cmdproc.setup()
511+
512+
print_location(cmdproc)
513+
458514
def five():
459515
from trepan.processor.cmdproc import CommandProcessor
460516
from trepan.processor.command.mock import MockDebugger
@@ -479,4 +535,8 @@ def five():
479535
"""
480536
)
481537

538+
import runpy
539+
540+
runpy._run_code(inside_frozen.__code__, {}, None)
541+
482542
exec("five()")

0 commit comments

Comments
 (0)