Skip to content

Commit fd96dc4

Browse files
committed
Allow breakpoints on not frozen modules
1 parent a2554d7 commit fd96dc4

2 files changed

Lines changed: 71 additions & 22 deletions

File tree

test/example/eval.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""
2+
Tests to see how well we do with eval/exec
3+
"""
4+
5+
# Deparsing will handle this
6+
eval_str = "1+2"
7+
x = eval(eval_str)
8+
print(x)
9+
10+
# When deparsing does not work, looking at the string to the
11+
# eval call will work
12+
x = eval("3+4")
13+
14+
exec("""y = 5;
15+
x += y
16+
""")
17+
print(x)
18+
19+
# TODO: add an AST example.

trepan/lib/breakpoint.py

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66

77
__all__ = ["BreakpointManager", "Breakpoint"]
88

9-
import os.path
10-
from types import CodeType
9+
import os.path as osp
10+
from types import CodeType, ModuleType
1111
from typing import Optional
12+
from xdis import load_module
1213

1314

1415
class Breakpoint:
@@ -38,7 +39,7 @@ def __init__(
3839

3940
self.filename = filename
4041
if filename:
41-
self.filename = os.path.realpath(filename)
42+
self.filename = osp.realpath(filename)
4243

4344
# Needed if funcname is not None.
4445
self.func_first_executable_line = None
@@ -70,12 +71,21 @@ def __str__(self):
7071
offset_str = " any"
7172
else:
7273
offset_str = "%4d" % self.offset
73-
msg = "%-4dbreakpoint %s %s at %s:%d" % (
74+
if self.offset is None:
75+
offset_str = " any"
76+
else:
77+
offset_str = "%4d" % self.offset
78+
if self.line is None:
79+
line_str = ""
80+
else:
81+
line_str = ":%d" % self.line
82+
83+
msg = "%-4dbreakpoint %s %s at %s%s" % (
7484
self.number,
7585
disp,
7686
offset_str,
7787
self.filename,
78-
self.line,
88+
line_str,
7989
)
8090
if self.condition:
8191
msg += f"\n\tstop only if {self.condition}"
@@ -175,20 +185,29 @@ def add_breakpoint(
175185
"""
176186
bpnum = len(self.bpbynumber)
177187
if filename:
178-
filename = os.path.realpath(filename)
188+
filename = osp.realpath(filename)
179189

180190
assert (
181191
isinstance(filename, str) or func is not None
182192
), "You must either supply a filename or give a line number"
183193

184194
if isinstance(func, CodeType):
185195
code = func
196+
elif isinstance(func, ModuleType):
197+
if hasattr(func, "__cached__"):
198+
# FIXME: we can probably do better hooking into importlib
199+
# or something lower-level
200+
_, _, _, code, _, _, _ = load_module(func.__cached__, fast_load=True, get_code=True)
201+
else:
202+
print(f"Don't know what to do with frozen module {func}")
203+
return
186204
elif hasattr(func, "__code__"):
187205
code = func.__code__
188206
elif hasattr(func, "f_code"):
189207
code = func.f_code
190208
else:
191209
print(f"Don't know what to do with {func}, {type(func)}")
210+
return
192211
brkpt = Breakpoint(bpnum, filename, lineno, temporary, condition, code, offset)
193212

194213
# Build the internal lists of breakpoints
@@ -392,15 +411,34 @@ def checkfuncname(brkpt: Breakpoint, frame):
392411
# Demo
393412

394413
if __name__ == "__main__":
414+
def foo(bp, bpmgr):
415+
frame = inspect.currentframe()
416+
assert frame
417+
print(f"Stop at bp2: {checkfuncname(bp, frame)}")
418+
# frame.f_lineno is constantly updated. So adjust for the
419+
# line difference between the add_breakpoint and the check.
420+
bp3 = bpmgr.add_breakpoint(__file__, 0, frame.f_lineno + 1, func=foo)
421+
print(f"Stop at bp3: {checkfuncname(bp3, frame)}")
422+
return
423+
424+
def bar() -> int:
425+
frame = inspect.currentframe()
426+
assert frame is not None
427+
return frame.f_lasti
428+
395429
bpmgr = BreakpointManager()
396430
print(bpmgr.last())
397-
bp = bpmgr.add_breakpoint("foo", 0, 5)
431+
line_number = foo.__code__.co_firstlineno
432+
bp = bpmgr.add_breakpoint(__file__, line_number, func=foo)
398433
print(bp.icon_char())
399434
print(bpmgr.last())
400435
print(repr(bp))
401436
print(str(bp))
402437
bp.disable()
403438
print(str(bp))
439+
import xdis
440+
bp = bpmgr.add_breakpoint(xdis.__file__, offset=0, func=xdis)
441+
print(bp)
404442
for i in 10, 1:
405443
status, msg = bpmgr.delete_breakpoint_by_number(i)
406444
print(
@@ -411,27 +449,19 @@ def checkfuncname(brkpt: Breakpoint, frame):
411449
frame = inspect.currentframe()
412450
print(f"Stop at bp: {checkfuncname(bp, frame)}")
413451

414-
def foo(bp, bpmgr):
415-
frame = inspect.currentframe()
416-
assert frame
417-
print(f"Stop at bp2: {checkfuncname(bp, frame)}")
418-
# frame.f_lineno is constantly updated. So adjust for the
419-
# line difference between the add_breakpoint and the check.
420-
bp3 = bpmgr.add_breakpoint("foo", 0, frame.f_lineno + 1)
421-
print(f"Stop at bp3: {checkfuncname(bp3, frame)}")
422-
return
423-
424452
bp2 = bpmgr.add_breakpoint(None, None, -1, False, None, foo)
425453
foo(bp2, bpmgr)
426-
bp3 = bpmgr.add_breakpoint("foo", 5, 2, temporary=True)
454+
bp3 = bpmgr.add_breakpoint(__file__, line_number, 0, temporary=True, func=foo)
427455
print(bp3.icon_char())
428456
print(bpmgr.bpnumbers())
429457

430-
bp = bpmgr.add_breakpoint("bar", 10, 3)
458+
line_number = bar.__code__.co_firstlineno
459+
bp = bpmgr.add_breakpoint(__file__, line_number, 0, func=bar)
431460
filename = bp.filename
432461
assert filename
462+
line_number = bar()
433463
for i in range(3):
434-
bp = bpmgr.add_breakpoint("bar", 2, 6)
435-
print(bpmgr.delete_breakpoints_by_lineno(filename, 6))
436-
print(bpmgr.delete_breakpoints_by_lineno(filename, 6))
464+
bp = bpmgr.add_breakpoint(None, None, line_number, func=bar)
465+
print(bpmgr.delete_breakpoints_by_lineno(filename, line_number))
466+
print(bpmgr.delete_breakpoints_by_lineno(filename, line_number))
437467
print(bpmgr.bpnumbers())

0 commit comments

Comments
 (0)