Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-103068: Check condition expression of breakpoints for pdb #103069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 26 additions & 12 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,7 @@ def user_exception(self, frame, exc_info):
# stop when the debuggee is returning from such generators.
prefix = 'Internal ' if (not exc_traceback
and exc_type is StopIteration) else ''
self.message('%s%s' % (prefix,
traceback.format_exception_only(exc_type, exc_value)[-1].strip()))
self.message('%s%s' % (prefix, self._format_exc(exc_value)))
self.interaction(frame, exc_traceback)

# General interaction function
Expand All @@ -399,7 +398,7 @@ def preloop(self):
displaying = self.displaying.get(self.curframe)
if displaying:
for expr, oldvalue in displaying.items():
newvalue, _ = self._getval_except(expr)
newvalue = self._getval_except(expr)
# check for identity first; this prevents custom __eq__ to
# be called at every loop, and also prevents instances whose
# fields are changed to be displayed
Expand Down Expand Up @@ -702,6 +701,9 @@ def do_break(self, arg, temporary = 0):
if comma > 0:
# parse stuff after comma: "condition"
cond = arg[comma+1:].lstrip()
if err := self._compile_error_message(cond):
self.error('Invalid condition %s: %r' % (cond, err))
return
arg = arg[:comma].rstrip()
# parse stuff before comma: [filename:]lineno | function
colon = arg.rfind(':')
Expand Down Expand Up @@ -887,6 +889,9 @@ def do_condition(self, arg):
args = arg.split(' ', 1)
try:
cond = args[1]
if err := self._compile_error_message(cond):
self.error('Invalid condition %s: %r' % (cond, err))
return
except IndexError:
cond = None
try:
Expand Down Expand Up @@ -1246,16 +1251,15 @@ def _getval(self, arg):
def _getval_except(self, arg, frame=None):
try:
if frame is None:
return eval(arg, self.curframe.f_globals, self.curframe_locals), None
return eval(arg, self.curframe.f_globals, self.curframe_locals)
else:
return eval(arg, frame.f_globals, frame.f_locals), None
return eval(arg, frame.f_globals, frame.f_locals)
except BaseException as exc:
err = traceback.format_exception_only(exc)[-1].strip()
return _rstr('** raised %s **' % err), exc
return _rstr('** raised %s **' % self._format_exc(exc))

def _error_exc(self):
exc_info = sys.exc_info()[:2]
self.error(traceback.format_exception_only(*exc_info)[-1].strip())
exc = sys.exc_info()[1]
self.error(self._format_exc(exc))

def _msg_val_func(self, arg, func):
try:
Expand Down Expand Up @@ -1443,10 +1447,10 @@ def do_display(self, arg):
else:
self.message('No expression is being displayed')
else:
val, exc = self._getval_except(arg)
if isinstance(exc, SyntaxError):
self.message('Unable to display %s: %r' % (arg, val))
if err := self._compile_error_message(arg):
self.error('Unable to display %s: %r' % (arg, err))
else:
val = self._getval_except(arg)
self.displaying.setdefault(self.curframe, {})[arg] = val
self.message('display %s: %r' % (arg, val))

Expand Down Expand Up @@ -1647,6 +1651,16 @@ def _run(self, target: Union[_ModuleTarget, _ScriptTarget]):

self.run(target.code)

def _format_exc(self, exc: BaseException):
return traceback.format_exception_only(exc)[-1].strip()

def _compile_error_message(self, expr):
"""Return the error message as string if compiling `expr` fails."""
try:
compile(expr, "<stdin>", "eval")
except SyntaxError as exc:
return _rstr(self._format_exc(exc))
return ""

# Collect all command help into docstring, if not run with -OO

Expand Down
18 changes: 17 additions & 1 deletion Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,11 @@ def test_pdb_breakpoint_commands():

>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
... 'break 3',
... 'break 4, +',
... 'disable 1',
... 'ignore 1 10',
... 'condition 1 1 < 2',
... 'condition 1 1 <',
... 'break 4',
... 'break 4',
... 'break',
Expand All @@ -264,19 +266,25 @@ def test_pdb_breakpoint_commands():
... 'commands 10', # out of range
... 'commands a', # display help
... 'commands 4', # already deleted
... 'break 6, undefined', # condition causing `NameError` during evaluation
... 'continue', # will stop, ignoring runtime error
... 'continue',
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>(3)test_function()
-> print(1)
(Pdb) break 3
Breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>:3
(Pdb) break 4, +
*** Invalid condition +: SyntaxError: invalid syntax
(Pdb) disable 1
Disabled breakpoint 1 at <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>:3
(Pdb) ignore 1 10
Will ignore next 10 crossings of breakpoint 1.
(Pdb) condition 1 1 < 2
New condition set for breakpoint 1.
(Pdb) condition 1 1 <
*** Invalid condition 1 <: SyntaxError: invalid syntax
(Pdb) break 4
Breakpoint 2 at <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>:4
(Pdb) break 4
Expand Down Expand Up @@ -331,8 +339,13 @@ def test_pdb_breakpoint_commands():
end
(Pdb) commands 4
*** cannot set commands: Breakpoint 4 already deleted
(Pdb) break 6, undefined
Breakpoint 5 at <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>:6
(Pdb) continue
3
> <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>(6)test_function()
-> print(4)
(Pdb) continue
4
"""

Expand Down Expand Up @@ -597,13 +610,14 @@ def test_pdb_display_command():
... 'undisplay',
... 'display a < 1',
... 'n',
... 'display undefined',
... 'continue',
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_display_command[0]>(4)test_function()
-> a = 1
(Pdb) display +
Unable to display +: ** raised SyntaxError: invalid syntax **
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have two separate error types for display now

  • ** raised <...> **
  • *** Unable to display <...>

I think it's worth covering ** raised <...> ** cases in the tests as well, for instance:

(Pdb) display undefined
display undefined: ** raised NameError: name 'undefined' is not defined **

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I noticed that break behaviour is different: pdb conservatively stops on the breakpoint ignoring the runtime errors. I think it's worth covering in the tests as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a suggestion for a test for the break behavior?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, here is a patch:

--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -266,6 +266,8 @@ def test_pdb_breakpoint_commands():
     ...     'commands 10',  # out of range
     ...     'commands a',   # display help
     ...     'commands 4',   # already deleted
+    ...     'break 6, undefined', # condition causing `NameError` during evaluation
+    ...     'continue', # will stop, ignoring runtime error
     ...     'continue',
     ... ]):
     ...    test_function()
@@ -337,8 +339,13 @@ def test_pdb_breakpoint_commands():
             end
     (Pdb) commands 4
     *** cannot set commands: Breakpoint 4 already deleted
+    (Pdb) break 6, undefined
+    Breakpoint 5 at <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>:6
     (Pdb) continue
     3
+    > <doctest test.test_pdb.test_pdb_breakpoint_commands[0]>(6)test_function()
+    -> print(4)
+    (Pdb) continue
     4
     """

*** Unable to display +: SyntaxError: invalid syntax
(Pdb) display
No expression is being displayed
(Pdb) display a
Expand All @@ -627,6 +641,8 @@ def test_pdb_display_command():
(Pdb) n
> <doctest test.test_pdb.test_pdb_display_command[0]>(7)test_function()
-> a = 4
(Pdb) display undefined
display undefined: ** raised NameError: name 'undefined' is not defined **
(Pdb) continue
"""

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
It's no longer possible to register conditional breakpoints in
:class:`~pdb.Pdb` that raise :exc:`SyntaxError`. Patch by Tian Gao.