Skip to content

Commit dca1196

Browse files
committed
Reinstate call breakpoints
1 parent 7270bef commit dca1196

File tree

4 files changed

+101
-70
lines changed

4 files changed

+101
-70
lines changed

trepan/lib/breakpoint.py

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -200,27 +200,15 @@ class BreakpointManager:
200200
201201
Breakpoints are indexed by number in the `bpbynumber' list, and
202202
through a (file,line) tuple which is a key in the `bplist'
203-
dictionary. If the breakpoint is a function it is in `code_list' as
203+
dictionary. If the breakpoint is a function it is in `code2position_brkpts' as
204204
well. Note there may be more than one breakpoint per line which
205205
may have different conditions associated with them.
206206
"""
207207

208208
def __init__(self):
209209

210-
# self.reset()
210+
self.reset()
211211

212-
# The below duplicates self.reset(). However we include it here,
213-
# to assist linters which as of 2014 largely do not grok attributes of
214-
# class unless it is put inside __init__
215-
216-
self.bpbynumber: list = [None]
217-
self.bplist = defaultdict(list)
218-
219-
# Keep a mapping from code object to breakpoints that are currently
220-
# active in that code. By keeping this mapping, we avoid
221-
# tracing frames that do not have breakpoints in their
222-
# corresponding code objects.
223-
self.code_list: DefaultDict[CodeType, list] = defaultdict(list)
224212
return
225213

226214
def bpnumbers(self):
@@ -264,6 +252,8 @@ def add_breakpoint(
264252
``temporary`` specifies whether the breakpoint will be removed once it is hit.
265253
`condition`` specifies that a string Python expression to be evaluated to determine
266254
whether the breakpoint is hit or not.
255+
256+
The parameter ``position`` is -1 when we want a breakpoint on a call event.
267257
"""
268258
bpnum = len(self.bpbynumber)
269259
if filename:
@@ -320,8 +310,14 @@ def add_breakpoint(
320310
# Build the internal lists of breakpoints
321311
self.bpbynumber.append(brkpt)
322312
self.bplist[filename, line_number].append(brkpt)
323-
if func_or_code and position != -1:
324-
self.code_list[code].append(brkpt)
313+
if func_or_code:
314+
if position == -1:
315+
# Add breakpoint to list of call event breakpoints,
316+
# indexed by code object.
317+
self.codecall_brkpts[code].append(brkpt)
318+
else:
319+
# Add breakpoint to list of breakpoints indexed by code object.
320+
self.code2position_brkpts[code].append(brkpt)
325321
return brkpt
326322

327323
def delete_all_breakpoints(self) -> str:
@@ -347,7 +343,12 @@ def delete_breakpoint(self, bp: Breakpoint) -> bool:
347343
if index not in self.bplist:
348344
return False
349345

350-
brkpts = self.code_list[bp.code]
346+
# FIXME: should mark breakpoint as being a call breakpoint or not instead of doing
347+
# this logic.
348+
brkpts = self.codecall_brkpts[bp.code] if bp.offset is None else self.code2position_brkpts[bp.code]
349+
if not brkpts:
350+
brkpts = self.code2position_brkpts[bp.code]
351+
351352
assert brkpts, f"Should have a list of breakpoints set in {bp.code}"
352353
if bp in brkpts:
353354
brkpts.remove(bp)
@@ -472,11 +473,21 @@ def reset(self):
472473
"""A list of breakpoints by breakpoint number. Each entry is
473474
None or an instance of Breakpoint. Index 0 is unused, except
474475
for marking an effective break .... see effective()."""
475-
self.bpbynumber = [None]
476+
self.bpbynumber: list = [None]
477+
478+
# Keep a mapping from code object to breakpoints that are currently
479+
# active in that code. By keeping this mapping, we avoid
480+
# tracing frames that do not have breakpoints in their
481+
# corresponding code objects.
482+
self.code2position_brkpts: DefaultDict[CodeType, list] = defaultdict(list)
476483

477-
# A list of breakpoints indexed by (file, line_number) tuple
478-
self.bplist = {}
479-
self.code_list = {}
484+
# Keep a mapping from code object for call events only to breakpoints that are currently
485+
# active in that code. By keeping this mapping, we avoid
486+
# tracing frames that do not have breakpoints in their
487+
# corresponding code objects.
488+
self.codecall_brkpts: DefaultDict[CodeType, list] = defaultdict(list)
489+
490+
self.bplist = defaultdict(list)
480491

481492
return
482493

trepan/lib/core.py

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@
5656
"ignore_filter": None, # But see debugger.py
5757
}
5858

59+
5960
class TrepanCore:
60-
def __init__(self, debugger, opts: InitOptions=DEFAULT_INIT_OPTS):
61+
def __init__(self, debugger, opts: InitOptions = DEFAULT_INIT_OPTS):
6162
"""Create a debugger object. But depending on the value of
6263
key 'start' inside hash `opts', we may or may not initially
6364
start tracing events (i.e. enter the debugger).
@@ -197,7 +198,7 @@ def canonic_filename(self, frame: Optional[FrameType]) -> str:
197198

198199
filename = frame.f_code.co_filename
199200
if "<string>" == filename:
200-
if (new_filename := pyficache.main.code2tempfile.get(frame.f_code)):
201+
if new_filename := pyficache.main.code2tempfile.get(frame.f_code):
201202
filename = new_filename
202203
return self.canonic(filename)
203204

@@ -231,7 +232,7 @@ def remove_ignore(self, frame_or_fn):
231232
be debugged"""
232233
return self.ignore_filter.remove(frame_or_fn)
233234

234-
def start(self, opts: InitOptions=DEFAULT_INIT_OPTS):
235+
def start(self, opts: InitOptions = DEFAULT_INIT_OPTS):
235236
"""We've already created a debugger object, but here we start
236237
debugging in earnest. We can also turn off debugging (but have
237238
the hooks suspended or not) using 'stop'.
@@ -292,32 +293,12 @@ def get_option(key: str) -> Any:
292293

293294
def is_break_here(self, frame):
294295
filename = self.canonic(frame.f_code.co_filename)
295-
if "call" == self.event:
296-
find_name = frame.f_code
297-
# Could check code object or decide not to
298-
# The below could be done as a list comprehension, but
299-
# I'm feeling in Fortran mood right now.
300-
for fn in self.bpmgr.code_list:
301-
if fn == find_name:
302-
bp_fn = self.bpmgr.code_list.get(fn)
303-
if not bp_fn:
304-
return False
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
310-
if bp.temporary:
311-
msg = "temporary "
312-
self.bpmgr.delete_breakpoint(bp)
313-
else:
314-
msg = ""
315-
pass
316-
self.stop_reason = f"at {msg}call breakpoint {bp.number}"
317-
self.event = "brkpt"
318-
return True
319-
pass
320-
pass
296+
code_object = frame.f_code
297+
brkpts_in_code = self.bpmgr.code2position_brkpts.get(code_object)
298+
299+
if brkpts_in_code is None:
300+
return False
301+
321302
if (filename, frame.f_lineno) in list(self.bpmgr.bplist.keys()):
322303
(bp, clear_bp) = self.bpmgr.find_bp(filename, frame.f_lineno, frame)
323304
if bp:
@@ -339,6 +320,25 @@ def is_break_here(self, frame):
339320
return False
340321
return False
341322

323+
def is_call_break_here(self, frame):
324+
code_object = frame.f_code
325+
brkpts_in_code = self.bpmgr.codecall_brkpts.get(code_object)
326+
327+
if brkpts_in_code is None:
328+
return False
329+
for bp in brkpts_in_code:
330+
self.current_bp = bp
331+
if bp.temporary:
332+
msg = "temporary "
333+
self.bpmgr.delete_breakpoint(bp)
334+
else:
335+
msg = ""
336+
pass
337+
self.stop_reason = f"at {msg}call breakpoint {bp.number}"
338+
self.event = "brkpt"
339+
return True
340+
return False
341+
342342
def matches_condition(self, frame):
343343
# Conditional bp.
344344
# Ignore count applies only to those bpt hits where the
@@ -450,19 +450,25 @@ def trace_dispatch(self, frame, event: str, arg):
450450
# for breakpoints in a kind of slow way of checking all events.
451451

452452
remove_frame_on_return = False
453+
is_call_breakpoint = False
453454
# FIXME:
454455
# instead of self.bpmgr.bplist == 0, we should be counting on
455456
# per breakpoints in code object.
456457
if event == "call":
457-
if (self.last_frame != frame
458+
if (
459+
self.last_frame != frame
458460
and len(self.bpmgr.bplist) == 0
459461
and self.stop_level is not None
460462
and self.stop_level < count_frames(frame)
461463
and self.current_thread == threading.current_thread()
462-
):
464+
):
463465
# We are "finish"ing or "next"ing and should not be tracing into this call
464466
# or any other calls from this. Return None to not trace further.
465-
# print("""XXX+ trace_dispatch: "finish"ing or "next"ing from call event""")
467+
468+
# print(
469+
# """XXX+ trace_dispatch: "finish"ing or "next"ing from call event"""
470+
# )
471+
466472
frame.f_trace = None
467473
return None
468474

@@ -474,7 +480,11 @@ def trace_dispatch(self, frame, event: str, arg):
474480
# this function. In this case we need to ignore this stop, but
475481
# make sure we don't turn off breakpoints inside this function which
476482
# we do by returning "self".
477-
return self
483+
if self.is_call_break_here(frame):
484+
is_call_breakpoint = True
485+
else:
486+
# print("XXX+ trace_dispatch: is_stop_here() return")
487+
return self
478488

479489
elif event == "return" and frame in FrameInfo:
480490
remove_frame_on_return = True
@@ -535,7 +545,11 @@ def trace_dispatch(self, frame, event: str, arg):
535545
# user's standpoint to test for breaks before steps. In
536546
# this case we will need to factor out the counting
537547
# updates.
538-
if self.is_stop_here(frame, event) or self.is_break_here(frame):
548+
if (
549+
self.is_stop_here(frame, event)
550+
or self.is_break_here(frame)
551+
or is_call_breakpoint
552+
):
539553
# Run the event processor
540554
return self.processor.event_processor(frame, self.event, arg)
541555
# else:
@@ -549,6 +563,7 @@ def trace_dispatch(self, frame, event: str, arg):
549563
pass
550564
if remove_frame_on_return:
551565
del FrameInfo[frame]
566+
552567
pass
553568

554569

trepan/processor/cmdproc.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -590,14 +590,17 @@ def process_commands(self):
590590
pass
591591
pass
592592
run_hooks(self, self.postcmd_hooks)
593-
if self.fast_continue and len(self.core.bpmgr.bplist) == 0:
594-
# Remove tracing on frames and remove trace hook.
595-
frame = self.curframe
596-
while frame:
597-
del frame.f_trace
598-
frame = frame.f_back
599-
self.debugger.intf[-1].msg("Fast continue...")
600-
remove_hook(self.core.trace_dispatch, True)
593+
if self.fast_continue:
594+
if len(self.core.bpmgr.bplist) == 0:
595+
# Remove tracing on frames and remove trace hook.
596+
frame = self.curframe
597+
while frame:
598+
del frame.f_trace
599+
frame = frame.f_back
600+
self.debugger.intf[-1].msg("Fast continue...")
601+
remove_hook(self.core.trace_dispatch, True)
602+
else:
603+
self.debugger.intf[-1].msg("Continue with breakpoint checking...")
601604

602605
return
603606

trepan/processor/location.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,16 +198,18 @@ def resolve_location(proc, location) -> Optional[Location]:
198198
proc.msg("Offset %d not found for line %d; using offset %d instead." %
199199
(location.offset, location.line_number, offset))
200200

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-
)
201+
pass
208202
pass
209203
else:
210204
return INVALID_LOCATION
205+
mod_or_func_or_code_name = lineinfo[0].name
206+
if mod_or_func_or_code.co_name != mod_or_func_or_code_name:
207+
# Breakpoint is in a nested function/method.
208+
# Get new code object
209+
mod_or_func_or_code = code_info.get(
210+
mod_or_func_or_code_name, mod_or_func_or_code
211+
)
212+
pass
211213
elif location.offset is not None:
212214
filename = frame2file(proc.core, curframe, canonic=False)
213215
is_address = True

0 commit comments

Comments
 (0)