Skip to content

Commit 2abdbad

Browse files
committed
Handle breakpoint() calls better.
* Record call as being in caller of trepan.api not in trepan.api * info breakpoints shows "breakpoint()" rather than "breakpoint" as the "type" * Don't allow deleting breakpoint(); instead suggest "disable"ing the breakpoint * Don't disallow fast continue if the only breakpoints we have are breakpoint() calls * Breakpoint icon for breakpoint() enabled is now "X" distinct from "B"; and disabled "x" distinct from "b".
1 parent 71a82f8 commit 2abdbad

6 files changed

Lines changed: 76 additions & 22 deletions

File tree

‎.pre-commit-config.yaml‎

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ repos:
55
- repo: https://github.com/pre-commit/pre-commit-hooks
66
rev: v4.5.0
77
hooks:
8-
- id: check-merge-conflict
9-
- id: debug-statements
10-
stages: [pre-commit]
11-
- id: end-of-file-fixer
12-
stages: [pre-commit]
8+
- id: check-merge-conflict
9+
- id: debug-statements
10+
stages: [pre-commit]
11+
exclude: ChangeLog-spell-corrected.diff|mathics/builtin/system.py
12+
- id: end-of-file-fixer
13+
stages: [pre-commit]
14+
exclude: ChangeLog-spell-corrected.diff
15+
- id: trailing-whitespace
16+
exclude: ChangeLog-spell-corrected.diff
1317
- repo: https://github.com/pycqa/isort
1418
rev: 5.13.2
1519
hooks:
@@ -18,12 +22,20 @@ repos:
1822
- repo: https://github.com/psf/black
1923
rev: 25.11.0
2024
hooks:
21-
- id: black
22-
language_version: python3
23-
stages: [pre-commit]
24-
additional_dependencies: ["click"]
25-
exclude: 'trepan/version.py'
25+
- id: black
26+
language_version: python3
27+
stages: [pre-commit]
28+
additional_dependencies: ["click"]
29+
exclude: 'trepan/version.py'
2630
- repo: https://github.com/python-jsonschema/check-jsonschema
2731
rev: 0.31.0 # Use the latest version
2832
hooks:
2933
- id: check-github-workflows
34+
- repo: https://github.com/pre-commit/mirrors-mypy
35+
rev: v1.11.2
36+
hooks:
37+
- id: mypy
38+
args:
39+
- --install-types
40+
- --ignore-missing-imports
41+
- --non-interactive

‎trepan/api.py‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
#
3-
# Copyright (C) 2008-2009, 2013-2017, 2019-2021, 2023-2025 Rocky
3+
# Copyright (C) 2008-2009, 2013-2017, 2019-2021, 2023-2026 Rocky
44
# Bernstein <rocky@gnu.org>
55
#
66
# This program is free software: you can redistribute it and/or modify
@@ -34,10 +34,13 @@
3434
# functions below. It also doesn't work once we add the exception handling
3535
# we see below. So for now, we'll live with the code duplication.
3636

37+
import inspect
3738
import os
3839
import sys
3940
import traceback
4041

42+
from typing import Optional
43+
4144
import trepan
4245
from trepan.debugger import Trepan
4346
from trepan.interfaces.server import ServerInterface
@@ -187,12 +190,24 @@ def debug(
187190

188191
bp = bpmgr.find_breakpoint(filename, line_number)
189192
if bp is None:
193+
194+
frame = inspect.currentframe()
195+
if frame is not None and frame.f_code.co_filename == filename:
196+
# Don't log the breakpoint as being in trepan.api. Instead, back one frame.
197+
frame = frame.f_back
198+
if frame is not None:
199+
code = frame.f_code
200+
filename = code.co_filename
201+
line_number = frame.f_lineno
202+
190203
bp = core.bpmgr.add_breakpoint(
191204
filename=filename,
192205
line_number=line_number,
193206
is_code_offset=False,
194207
condition=None,
195-
func_or_code=code)
208+
func_or_code=code,
209+
is_breakpoint_call=True
210+
)
196211
elif not bp.enabled:
197212
core.step_ignore = -1
198213
return
@@ -232,7 +247,7 @@ def debugger_on_post_mortem():
232247
return
233248

234249

235-
def run_call(func, *args, debug_opts=DEBUGGER_SETTINGS, start_opts=None, **kwds):
250+
def run_call(func, *args, debug_opts=DEBUGGER_SETTINGS, start_opts:Optional[dict]=None, **kwds):
236251
"""Call the function (a function or method object, not a string)
237252
with the given arguments starting with the statement after
238253
the place that this appears in your program.
@@ -242,6 +257,8 @@ def run_call(func, *args, debug_opts=DEBUGGER_SETTINGS, start_opts=None, **kwds)
242257
entered."""
243258

244259
dbg = Trepan(opts=debug_opts)
260+
if start_opts is not None:
261+
kwds["start_opts"] = start_opts
245262
try:
246263
return dbg.run_call(func, *args, **kwds)
247264
except Exception:

‎trepan/debugger.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
#
3-
# Copyright (C) 2008-2010, 2013-2015, 2018, 2023-2025
3+
# Copyright (C) 2008-2010, 2013-2015, 2018, 2023-2026
44
# Rocky Bernstein <rocky@gnu.org>
55
#
66
# This program is free software: you can redistribute it and/or modify
@@ -31,7 +31,7 @@
3131

3232
import sys
3333
import types
34-
from typing import Any, Callable, Union
34+
from typing import Any, Callable, Optional, Union
3535

3636
import pyficache
3737
import tracer
@@ -169,7 +169,7 @@ def complete(self, last_token: str, state: int):
169169
return results[state]
170170
return
171171

172-
def run(self, cmd, start_opts=None, globals_=None, locals_=None):
172+
def run(self, cmd, start_opts: Optional[dict]=None, globals_=None, locals_=None):
173173
"""Run debugger on string `cmd' using builtin function eval
174174
and if that builtin exec. Arguments `globals_' and `locals_'
175175
are the dictionaries to use for local and global variables. By
@@ -238,7 +238,7 @@ def run_exec(self, cmd, start_opts=None, globals_=None, locals_=None):
238238
self.core.stop()
239239
return
240240

241-
def run_call(self, func: Callable, *args, start_opts=None, **kwds):
241+
def run_call(self, func: Callable, *args, start_opts: Optional[dict]=None, **kwds):
242242
"""Run debugger on function call: `func(*args, **kwds)'
243243
244244
See also ``run_eval`` if what you want to run is an eval'able

‎trepan/lib/breakpoint.py‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def __init__(
6262
code: Optional[CodeType] = None,
6363
position: Optional[int] = None,
6464
is_code_offset: bool = True,
65+
is_breakpoint_call: bool = False
6566
):
6667
# FIXME: split out this top part into a part that fills out information
6768
if code is not None:
@@ -122,6 +123,8 @@ def __init__(
122123
# Number of times to ignore breakpoint before stopping
123124
self.ignore = 0
124125

126+
self.is_breakpoint_call = is_breakpoint_call
127+
125128
self.line_number = line_number
126129
self.number = bp_number
127130

@@ -183,9 +186,13 @@ def icon_char(self) -> str:
183186
't': temporary breakpoint
184187
'B': enabled breakpoint
185188
'b': disabled breakpoint
189+
'x': disabled breakpoint()
190+
'X': enabled breakpoint()
186191
"""
187192
if self.temporary:
188193
return "t"
194+
elif self.is_breakpoint_call:
195+
return "X" if self.enabled else "x"
189196
elif self.enabled:
190197
return "B"
191198
return "b"
@@ -209,7 +216,7 @@ def __init__(self):
209216

210217
return
211218

212-
def bpnumbers(self):
219+
def bpnumbers(self)-> list[int]:
213220
"""Returns a list of strings of breakpoint numbers"""
214221
return ["%d" % bp.number for bp in self.bpbynumber if bp is not None]
215222

@@ -231,7 +238,7 @@ def get_breakpoint(self, i) -> tuple:
231238
)
232239
bp = self.bpbynumber[i]
233240
if bp is None:
234-
return (False, "Breakpoint %d previously deleted." % i, None)
241+
return (False, f"Breakpoint {i} previously deleted.", None)
235242
return (True, None, bp)
236243

237244
def add_breakpoint(
@@ -243,6 +250,7 @@ def add_breakpoint(
243250
temporary: bool = False,
244251
condition: Optional[str] = None,
245252
func_or_code=None,
253+
is_breakpoint_call: bool = False,
246254
):
247255
"""
248256
Add a breakpoint in ``filename`` at line number ``line_number``.
@@ -254,6 +262,7 @@ def add_breakpoint(
254262
The parameter ``position`` is -1 when we want a breakpoint on a call event.
255263
"""
256264
bpnum = len(self.bpbynumber)
265+
257266
if filename:
258267
filename = osp.realpath(filename)
259268

@@ -303,6 +312,7 @@ def add_breakpoint(
303312
code,
304313
position,
305314
is_code_offset,
315+
is_breakpoint_call,
306316
)
307317

308318
# Build the internal lists of breakpoints
@@ -364,6 +374,8 @@ def delete_breakpoint_by_number(self, bpnum: int) -> tuple:
364374
success, msg, bp = self.get_breakpoint(bpnum)
365375
if not success:
366376
return False, msg
377+
if bp.is_breakpoint_call:
378+
return False, "Cannot delete a breakpoint() call; use disable instead."
367379
self.delete_breakpoint(bp)
368380
return (True, "")
369381

@@ -480,6 +492,15 @@ def find_breakpoint(self, filename: str, line_number: int) -> Optional[Breakpoin
480492
return b
481493
return None
482494

495+
def needs_no_tracing(self) -> bool:
496+
"""
497+
Return True if all breakpoints do not need any sys.tracing
498+
support, i.e. they are handled either by explicit breakpoint() calls
499+
or the newer debug protocal handles the breakpoints
500+
"""
501+
# return len(self.bplist) == 0
502+
return all(not getattr(item, 'is_breakpoint_call', False) for item in self.bplist)
503+
483504
def last(self):
484505
return len(self.bpbynumber) - 1
485506

‎trepan/processor/cmdproc.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ def process_commands(self):
592592
pass
593593
run_hooks(self, self.postcmd_hooks)
594594
if self.fast_continue:
595-
if len(self.core.bpmgr.bplist) == 0:
595+
if self.core.bpmgr.needs_no_tracing:
596596
# Remove tracing on frames and remove trace hook.
597597
frame = self.curframe
598598
while frame:

‎trepan/processor/command/info_subcmd/breakpoints.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class InfoBreakpoints(Mbase_subcmd.DebuggerSubcommand):
4848
breakpoint already hit 1 time
4949
3 breakpoint keep y 20 at /tmp/fib.py:6
5050
stop only if x > 0
51+
4 breakpoint() keep y 30 at /tmp/fib.py:6
5152
5253
See also:
5354
---------
@@ -76,11 +77,13 @@ def bpprint(self, bp):
7677
column_str = ":%d" % (bp.column + 1)
7778
else:
7879
column_str = ""
80+
brkpt_type = "breakpoint()" if bp.is_breakpoint_call else "breakpoint"
7981
if bp.offset is None:
8082
self.msg(
81-
"%-4dbreakpoint %s any at %s:%d%s"
83+
"%-4d%-12s %s any at %s:%d%s"
8284
% (
8385
bp.number,
86+
brkpt_type,
8487
disp,
8588
self.core.filename(bp.filename),
8689
bp.line_number,
@@ -89,9 +92,10 @@ def bpprint(self, bp):
8992
)
9093
else:
9194
self.msg(
92-
"%-4dbreakpoint %s %4s at %s:%d%s"
95+
"%-4d%-12s %s %4s at %s:%d%s"
9396
% (
9497
bp.number,
98+
brkpt_type,
9599
disp,
96100
"*" + str(bp.offset),
97101
self.core.filename(bp.filename),

0 commit comments

Comments
 (0)