Skip to content

Commit 3bd9c0a

Browse files
committed
Start allowing column number breakpoints
1 parent c7419d3 commit 3bd9c0a

File tree

6 files changed

+103
-55
lines changed

6 files changed

+103
-55
lines changed

test/unit/processor/test_proc_complete.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def test_completion():
6363
"info ",
6464
[
6565
"args",
66-
"break",
66+
"breakpoints",
6767
"builtins",
6868
"code",
6969
"display",

trepan/lib/breakpoint.py

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
# -*- coding: utf-8 -*-
2-
# Copyright (C) 2015, 2017, 2020, 2024-2026 Rocky Bernstein <[email protected]>
3-
#
42
# Copyright (C) 2009-2010, 2013, 2015-2018, 2020, 2022, 2024-2026 Rocky Bernstein
53
# This program is free software: you can redistribute it and/or modify
64
# it under the terms of the GNU General Public License as published by
@@ -29,7 +27,6 @@
2927
from pyficache import (
3028
code_position_cache,
3129
code_loop_for_positions,
32-
get_linecache_info,
3330
)
3431
from xdis import load_module
3532

@@ -78,36 +75,29 @@ def __init__(
7875
# code.co_filename? Probably not: trepan3k and pyficache do allow for
7976
# remapping filenames.
8077

81-
linecache_info = get_linecache_info(filename)
82-
8378
if is_code_offset and position and position >= 0:
8479
self.column = None
8580
# Figure out column offset, and possibly the code offset.
86-
if (code_offset_tuples := linecache_info.line_info.get(line_number)) is not None:
87-
if found := next((item for item in code_offset_tuples if item[-1] == position), None):
88-
self.offset = position
89-
found_code = found[0]
90-
91-
# Figure out column offset
92-
if found_code not in code_position_cache:
93-
code_loop_for_positions(found_code)
94-
95-
if (code_info := code_position_cache.get(found_code)) is not None:
96-
# Now get the column start value from the line number and code offset.
97-
column_range = code_info.lineno_and_offset.get((line_number, self.offset))
98-
if column_range:
99-
assert isinstance(column_range, tuple)
100-
start_line, self.column = column_range[0]
101-
# Python stores columns starting 0 in a line.
102-
# For realgud and lldb compatibility (among others),
103-
# we use columns starting at 1.
104-
self.column += 1
105-
assert start_line == line_number
106-
pass
107-
else:
108-
print("Can't find column")
109-
pass
81+
self.offset = position
82+
83+
# Figure out column offset
84+
if code not in code_position_cache:
85+
code_loop_for_positions(code)
86+
87+
if (code_info := code_position_cache.get(code)) is not None:
88+
# Now get the column start value from the line number and code offset.
89+
column_range = code_info.lineno_and_offset.get((line_number, self.offset))
90+
if column_range:
91+
assert isinstance(column_range, tuple)
92+
start_line, self.column = column_range[0]
93+
# Python stores columns starting 0 in a line.
94+
# For realgud and lldb compatibility (among others),
95+
# we use columns starting at 1.
96+
self.column += 1
97+
assert start_line == line_number
11098
pass
99+
else:
100+
print(f"Can't find column for line {line_number}, offset {self.offset}")
111101
pass
112102
pass
113103
else:
@@ -152,7 +142,7 @@ def __str__(self):
152142
disp = disp + "no "
153143

154144
if self.offset is None:
155-
offset_str = " any"
145+
offset_str = " any"
156146
else:
157147
offset_str = "%4d" % self.offset
158148

@@ -495,7 +485,7 @@ def checkfuncname(brkpt: Breakpoint, frame: FrameType):
495485

496486
# Breakpoint set via function code object
497487

498-
if frame.f_code != brkpt.code:
488+
if frame.f_code != brkpt.code and brkpt.offset is not None:
499489
# It's not a function call, but rather execution of def statement.
500490
return False
501491

trepan/lib/core.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,11 @@ def is_break_here(self, frame):
302302
bp_fn = self.bpmgr.code_list.get(fn)
303303
if not bp_fn:
304304
return False
305-
self.current_bp = bp = bp_fn[0]
305+
bp = bp_fn[0]
306+
if bp.offset is not None and bp.offset != frame.f_lasti:
307+
# print(f"XXXX core: have breakpoint, but offsets mismatch {bp.offset} vs {frame.f_lasti}")
308+
return False
309+
self.current_bp = bp
306310
if bp.temporary:
307311
msg = "temporary "
308312
self.bpmgr.delete_breakpoint(bp)
@@ -317,6 +321,10 @@ def is_break_here(self, frame):
317321
if (filename, frame.f_lineno) in list(self.bpmgr.bplist.keys()):
318322
(bp, clear_bp) = self.bpmgr.find_bp(filename, frame.f_lineno, frame)
319323
if bp:
324+
if bp.offset is not None and bp.offset != frame.f_lasti:
325+
# print(f"XXXX core: have breakpoint, but offsets mismatch {bp.offset} vs {frame.f_lasti}")
326+
return False
327+
320328
self.current_bp = bp
321329
if clear_bp and bp.temporary:
322330
msg = "temporary "

trepan/processor/cmdbreak.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def set_break(
3636
force=False,
3737
offset=None,
3838
):
39+
3940
if line_number is None and offset is None:
4041
part1 = f"""I don't understand '{" ".join(args[1:])}' as a line number, offset, or function name"""
4142
msg = wrapped_lines(

trepan/processor/command/info_subcmd/break.py renamed to trepan/processor/command/info_subcmd/breakpoints.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
# Copyright (C) 2009, 2012-2013, 2015, 2018, 2024
2+
# Copyright (C) 2009, 2012-2013, 2015, 2018, 2024, 2026
33
# Rocky Bernstein <[email protected]>
44
#
55
# This program is free software: you can redistribute it and/or modify
@@ -21,7 +21,7 @@
2121
from trepan.processor.command import base_subcmd as Mbase_subcmd
2222

2323

24-
class InfoBreak(Mbase_subcmd.DebuggerSubcommand):
24+
class InfoBreakpoints(Mbase_subcmd.DebuggerSubcommand):
2525
"""**info breakpoints** [ *bp-number...* ]
2626
2727
Show the status of specified breakpoints (or all user-settable
@@ -55,10 +55,9 @@ class InfoBreak(Mbase_subcmd.DebuggerSubcommand):
5555
5656
"""
5757

58-
min_abbrev = len("b")
58+
min_abbrev = len("br")
5959
need_stack = False
6060
short_help = "Status of user-settable breakpoints"
61-
6261
complete = complete_bpnumber
6362

6463
def bpprint(self, bp):
@@ -79,13 +78,26 @@ def bpprint(self, bp):
7978
column_str = ""
8079
if bp.offset is None:
8180
self.msg(
82-
"%-4dbreakpoint %s at %s:%d%s"
83-
% (bp.number, disp, self.core.filename(bp.filename), bp.line_number, column_str)
81+
"%-4dbreakpoint %s any at %s:%d%s"
82+
% (
83+
bp.number,
84+
disp,
85+
self.core.filename(bp.filename),
86+
bp.line_number,
87+
column_str,
88+
)
8489
)
8590
else:
8691
self.msg(
8792
"%-4dbreakpoint %s %4s at %s:%d%s"
88-
% (bp.number, disp, "*"+str(bp.offset), self.core.filename(bp.filename), bp.line_number, column_str)
93+
% (
94+
bp.number,
95+
disp,
96+
"*" + str(bp.offset),
97+
self.core.filename(bp.filename),
98+
bp.line_number,
99+
column_str,
100+
)
89101
)
90102
if bp.condition:
91103
self.msg(f"\tstop only if {bp.condition}")

trepan/processor/location.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
#
3-
# Copyright (C) 2017, 2020, 2023-2024 Rocky Bernstein
3+
# Copyright (C) 2017, 2020, 2023-2024, 2026 Rocky Bernstein
44
# This program is free software: you can redistribute it and/or modify
55
# it under the terms of the GNU General Public License as published by
66
# the Free Software Foundation, either version 3 of the License, or
@@ -98,7 +98,9 @@ def resolve_location(proc, location) -> Optional[Location]:
9898
# trepan-xpy() which has it's own type of compatible
9999
# Function, that would fail an `inspect.isfunction()`
100100
# test.
101-
if hasattr(mod_or_func_or_code, "__code__") or hasattr(mod_or_func_or_code, "im_func"):
101+
if hasattr(mod_or_func_or_code, "__code__") or hasattr(
102+
mod_or_func_or_code, "im_func"
103+
):
102104
offset = -1
103105
else:
104106
proc.errmsg(msg)
@@ -129,14 +131,18 @@ def resolve_location(proc, location) -> Optional[Location]:
129131
is_address = location.is_address
130132
if inspect.ismodule(mod_or_func_or_code):
131133
if hasattr(mod_or_func_or_code, "__file__"):
132-
filename = pyficache.resolve_name_to_path(mod_or_func_or_code.__file__)
134+
filename = pyficache.resolve_name_to_path(
135+
mod_or_func_or_code.__file__
136+
)
133137
filename = proc.core.canonic(filename)
134138
if not lineno:
135139
# use first line of module file
136140
lineno = 1
137141
offset = 0
138142
is_address = False
139-
return Location(filename, lineno, is_address, mod_or_func_or_code, offset)
143+
return Location(
144+
filename, lineno, is_address, mod_or_func_or_code, offset
145+
)
140146
else:
141147
msg = f"module '{location.path}' doesn't have a file associated with it"
142148

@@ -159,7 +165,6 @@ def resolve_location(proc, location) -> Optional[Location]:
159165
else:
160166
return INVALID_LOCATION
161167

162-
163168
elif location.line_number:
164169
if curframe is None:
165170
proc.errmsg("Current frame is not set")
@@ -169,14 +174,37 @@ def resolve_location(proc, location) -> Optional[Location]:
169174
is_address = location.is_address
170175
mod_or_func_or_code = curframe.f_code
171176
if offset is None:
172-
code_info, lineinfo = pyficache.code_line_info(filename, lineno, include_offsets=True)
177+
code_info, lineinfo = pyficache.code_line_info(
178+
filename, lineno, include_offsets=True
179+
)
173180
if lineinfo:
174-
offset = lineinfo[0].offsets[0]
175-
mod_or_func_or_code_name = lineinfo[0].name
176-
if mod_or_func_or_code.co_name != mod_or_func_or_code_name:
177-
# Breakpoint is in a nested function/method.
178-
# Get new code object
179-
mod_or_func_or_code = code_info.get(mod_or_func_or_code_name, mod_or_func_or_code)
181+
if location.offset is not None:
182+
# Find the more specific offset.
183+
desired_offset = location.offset
184+
for linegroup in lineinfo:
185+
offset = next(
186+
(
187+
try_offset
188+
for try_offset in linegroup.offsets
189+
if try_offset == desired_offset
190+
),
191+
None
192+
)
193+
if offset is not None:
194+
break
195+
pass
196+
else:
197+
offset = lineinfo[0].offsets[0]
198+
proc.msg("Offset %d not found for line %d; using offset %d instead." %
199+
(location.offset, location.line_number, offset))
200+
201+
mod_or_func_or_code_name = lineinfo[0].name
202+
if mod_or_func_or_code.co_name != mod_or_func_or_code_name:
203+
# Breakpoint is in a nested function/method.
204+
# Get new code object
205+
mod_or_func_or_code = code_info.get(
206+
mod_or_func_or_code_name, mod_or_func_or_code
207+
)
180208
pass
181209
else:
182210
return INVALID_LOCATION
@@ -227,7 +255,9 @@ def resolve_address_location(proc, location) -> Optional[Location]:
227255

228256
try:
229257
# Check if the converted string is a function or instance method
230-
if inspect.isfunction(mod_or_func_or_code) or hasattr(mod_or_func_or_code, "im_func"):
258+
if inspect.isfunction(mod_or_func_or_code) or hasattr(
259+
mod_or_func_or_code, "im_func"
260+
):
231261
pass
232262
else:
233263
proc.errmsg(msg)
@@ -257,13 +287,17 @@ def resolve_address_location(proc, location) -> Optional[Location]:
257287
is_address = location.is_address
258288
if inspect.ismodule(mod_or_func_or_code):
259289
if hasattr(mod_or_func_or_code, "__file__"):
260-
filename = pyficache.resolve_name_to_path(mod_or_func_or_code.__file__)
290+
filename = pyficache.resolve_name_to_path(
291+
mod_or_func_or_code.__file__
292+
)
261293
filename = proc.core.canonic(filename)
262294
if not offset:
263295
# use first offset of module file
264296
offset = 0
265297
is_address = True
266-
return Location(filename, offset, is_address, mod_or_func_or_code, offset)
298+
return Location(
299+
filename, offset, is_address, mod_or_func_or_code, offset
300+
)
267301
else:
268302
msg = f"module '{location.path}' doesn't have a file associated with it"
269303

@@ -304,6 +338,9 @@ def resolve_address_location(proc, location) -> Optional[Location]:
304338
cmdproc = CommandProcessor(d.core)
305339
frame = cmdproc.frame = sys._getframe()
306340
cmdproc.setup()
341+
location = Location(__file__, 2, False, None, None)
342+
assert resolve_location(cmdproc, location) == INVALID_LOCATION
343+
307344
location = Location(__file__, frame.f_lineno, False, None, frame.f_lasti)
308345
print(resolve_location(cmdproc, location))
309346
location = Location(__file__, None, False, "resolve_location", frame.f_lasti)

0 commit comments

Comments
 (0)