From d3727a69230630e9c2cac38c68fc06cb0366340e Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 27 Mar 2023 22:03:06 -0700 Subject: [PATCH 1/7] Check condition expression for breakpoints --- Lib/pdb.py | 31 ++++++++++++++++++++++++------- Lib/test/test_pdb.py | 8 +++++++- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index d402de1192f9e2..5fb3635aff554c 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -399,7 +399,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 @@ -702,6 +702,9 @@ def do_break(self, arg, temporary = 0): if comma > 0: # parse stuff after comma: "condition" cond = arg[comma+1:].lstrip() + if err := self.checkexpr(cond): + self.error('Invalid condition %s: %r' % (cond, err)) + return arg = arg[:comma].rstrip() # parse stuff before comma: [filename:]lineno | function colon = arg.rfind(':') @@ -840,6 +843,17 @@ def checkline(self, filename, lineno): return 0 return lineno + def checkexpr(self, expr): + """ Check whether `expr` is a valid expression + + Return the error message if there's a syntax error, otherwise None + """ + try: + compile(expr, "", "eval") + except SyntaxError as exc: + return _rstr(traceback.format_exception_only(exc)[-1].strip()) + return None + def do_enable(self, arg): """enable bpnumber [bpnumber ...] Enables the breakpoints given as a space separated list of @@ -887,6 +901,9 @@ def do_condition(self, arg): args = arg.split(' ', 1) try: cond = args[1] + if err := self.checkexpr(cond): + self.error('Invalid condition %s: %r' % (cond, err)) + return except IndexError: cond = None try: @@ -1246,12 +1263,12 @@ 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 **' % err) def _error_exc(self): exc_info = sys.exc_info()[:2] @@ -1443,10 +1460,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.checkexpr(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)) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index ae9c5d73e2daa7..6fdde25bdb8c29 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -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', @@ -271,12 +273,16 @@ def test_pdb_breakpoint_commands(): -> print(1) (Pdb) break 3 Breakpoint 1 at :3 + (Pdb) break 4, + + *** Invalid condition +: SyntaxError: invalid syntax (Pdb) disable 1 Disabled breakpoint 1 at :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 :4 (Pdb) break 4 @@ -603,7 +609,7 @@ def test_pdb_display_command(): > (4)test_function() -> a = 1 (Pdb) display + - Unable to display +: ** raised SyntaxError: invalid syntax ** + *** Unable to display +: SyntaxError: invalid syntax (Pdb) display No expression is being displayed (Pdb) display a From 71190cf185c2147259b0918a8adf0faba97148fa Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 28 Mar 2023 05:15:01 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst diff --git a/Misc/NEWS.d/next/Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst b/Misc/NEWS.d/next/Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst new file mode 100644 index 00000000000000..7c969922448886 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst @@ -0,0 +1 @@ +Breakpoint's condition expression is checked for SyntaxError on :mod:`pdb`. From 70925bbe0f02bd07a6889a2a57a13289ed645684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Tue, 28 Mar 2023 11:14:19 +0200 Subject: [PATCH 3/7] Make news entry consistent with GH-103023 --- .../Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst b/Misc/NEWS.d/next/Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst index 7c969922448886..71c142c30f4eff 100644 --- a/Misc/NEWS.d/next/Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst +++ b/Misc/NEWS.d/next/Library/2023-03-28-05-14-59.gh-issue-103068.YQTmrA.rst @@ -1 +1,2 @@ -Breakpoint's condition expression is checked for SyntaxError on :mod:`pdb`. +It's no longer possible to register conditional breakpoints in +:class:`~pdb.Pdb` that raise :exc:`SyntaxError`. Patch by Tian Gao. From 5385096a2746ac326149d0108ad838f25bc4ffce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Tue, 28 Mar 2023 18:09:35 +0200 Subject: [PATCH 4/7] Since Python 3.10 (see GH-22610) `format_exception_only` only uses `exc_value` --- Lib/pdb.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 5fb3635aff554c..80a02d0fcdb21e 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -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 @@ -851,7 +850,7 @@ def checkexpr(self, expr): try: compile(expr, "", "eval") except SyntaxError as exc: - return _rstr(traceback.format_exception_only(exc)[-1].strip()) + return _rstr(self._format_exc(exc)) return None def do_enable(self, arg): @@ -1267,12 +1266,11 @@ def _getval_except(self, arg, frame=None): else: 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) + 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: @@ -1664,6 +1662,9 @@ 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() + # Collect all command help into docstring, if not run with -OO From 80283fc6b284be60151edabaf4eefcb1f2f3a32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Tue, 28 Mar 2023 18:27:21 +0200 Subject: [PATCH 5/7] Rename `checkexpr` to `_compile_error_message` --- Lib/pdb.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 80a02d0fcdb21e..3a06cd00ad2bf1 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -701,7 +701,7 @@ def do_break(self, arg, temporary = 0): if comma > 0: # parse stuff after comma: "condition" cond = arg[comma+1:].lstrip() - if err := self.checkexpr(cond): + if err := self._compile_error_message(cond): self.error('Invalid condition %s: %r' % (cond, err)) return arg = arg[:comma].rstrip() @@ -842,17 +842,6 @@ def checkline(self, filename, lineno): return 0 return lineno - def checkexpr(self, expr): - """ Check whether `expr` is a valid expression - - Return the error message if there's a syntax error, otherwise None - """ - try: - compile(expr, "", "eval") - except SyntaxError as exc: - return _rstr(self._format_exc(exc)) - return None - def do_enable(self, arg): """enable bpnumber [bpnumber ...] Enables the breakpoints given as a space separated list of @@ -900,7 +889,7 @@ def do_condition(self, arg): args = arg.split(' ', 1) try: cond = args[1] - if err := self.checkexpr(cond): + if err := self._compile_error_message(cond): self.error('Invalid condition %s: %r' % (cond, err)) return except IndexError: @@ -1458,7 +1447,7 @@ def do_display(self, arg): else: self.message('No expression is being displayed') else: - if err := self.checkexpr(arg): + if err := self._compile_error_message(arg): self.error('Unable to display %s: %r' % (arg, err)) else: val = self._getval_except(arg) @@ -1665,6 +1654,13 @@ def _run(self, target: Union[_ModuleTarget, _ScriptTarget]): 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, "", "eval") + except SyntaxError as exc: + return _rstr(self._format_exc(exc)) + return "" # Collect all command help into docstring, if not run with -OO From c7460b3fa8043f6823f97f2b979954030b4ffa51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Tue, 28 Mar 2023 18:28:49 +0200 Subject: [PATCH 6/7] Add test for a temporarily invalid display --- Lib/test/test_pdb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 6fdde25bdb8c29..dfb1b2f4829265 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -603,6 +603,7 @@ def test_pdb_display_command(): ... 'undisplay', ... 'display a < 1', ... 'n', + ... 'display undefined', ... 'continue', ... ]): ... test_function() @@ -633,6 +634,8 @@ def test_pdb_display_command(): (Pdb) n > (7)test_function() -> a = 4 + (Pdb) display undefined + display undefined: ** raised NameError: name 'undefined' is not defined ** (Pdb) continue """ From b38cf088f71d72e4da1d72701b96c568801ec0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Wed, 29 Mar 2023 02:59:04 +0200 Subject: [PATCH 7/7] Add test for a breakpoint with a failing expression Co-authored-by: Artem Mukhin --- Lib/test/test_pdb.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index dfb1b2f4829265..de2bab46495729 100644 --- 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 :6 (Pdb) continue 3 + > (6)test_function() + -> print(4) + (Pdb) continue 4 """