From 989aa447d79c010861b19053fad1204429aede38 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:59:11 +0200 Subject: [PATCH] [3.12] gh-65052: Prevent pdb from crashing when trying to display objects (GH-110578) (#110734) gh-65052: Prevent pdb from crashing when trying to display objects (GH-110578) (cherry picked from commit c523ce0f434582580a3721e15cb7dd6b56ad0236) Co-authored-by: Tian Gao --- Lib/pdb.py | 21 ++++++--- Lib/test/test_pdb.py | 47 +++++++++++++++++++ ...3-10-09-19-09-32.gh-issue-65052.C2mRlo.rst | 1 + 3 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-09-19-09-32.gh-issue-65052.C2mRlo.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 646695ccd1f4dd..df01829b48460e 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -410,8 +410,9 @@ def preloop(self): # fields are changed to be displayed if newvalue is not oldvalue and newvalue != oldvalue: displaying[expr] = newvalue - self.message('display %s: %r [old: %r]' % - (expr, newvalue, oldvalue)) + self.message('display %s: %s [old: %s]' % + (expr, self._safe_repr(newvalue, expr), + self._safe_repr(oldvalue, expr))) def interaction(self, frame, traceback): # Restore the previous signal handler at the Pdb prompt. @@ -1264,7 +1265,7 @@ def do_args(self, arg): for i in range(n): name = co.co_varnames[i] if name in dict: - self.message('%s = %r' % (name, dict[name])) + self.message('%s = %s' % (name, self._safe_repr(dict[name], name))) else: self.message('%s = *** undefined ***' % (name,)) do_a = do_args @@ -1275,7 +1276,7 @@ def do_retval(self, arg): Print the return value for the last return of a function. """ if '__return__' in self.curframe_locals: - self.message(repr(self.curframe_locals['__return__'])) + self.message(self._safe_repr(self.curframe_locals['__return__'], "retval")) else: self.error('Not yet returned!') do_rv = do_retval @@ -1310,6 +1311,12 @@ def _msg_val_func(self, arg, func): except: self._error_exc() + def _safe_repr(self, obj, expr): + try: + return repr(obj) + except Exception as e: + return _rstr(f"*** repr({expr}) failed: {self._format_exc(e)} ***") + def do_p(self, arg): """p expression @@ -1486,8 +1493,8 @@ def do_display(self, arg): if not arg: if self.displaying: self.message('Currently displaying:') - for item in self.displaying.get(self.curframe, {}).items(): - self.message('%s: %r' % item) + for key, val in self.displaying.get(self.curframe, {}).items(): + self.message('%s: %s' % (key, self._safe_repr(val, key))) else: self.message('No expression is being displayed') else: @@ -1496,7 +1503,7 @@ def do_display(self, arg): else: val = self._getval_except(arg) self.displaying.setdefault(self.curframe, {})[arg] = val - self.message('display %s: %r' % (arg, val)) + self.message('display %s: %s' % (arg, self._safe_repr(val, arg))) complete_display = _complete_expression diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index c0fb61253be5ad..46f9d6ef8a1d44 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -1846,6 +1846,53 @@ def test_pdb_ambiguous_statements(): (Pdb) continue """ +def test_pdb_issue_gh_65052(): + """See GH-65052 + + args, retval and display should not crash if the object is not displayable + >>> class A: + ... def __new__(cls): + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... return object.__new__(cls) + ... def __init__(self): + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... self.a = 1 + ... def __repr__(self): + ... return self.a + + >>> def test_function(): + ... A() + >>> with PdbTestInput([ # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + ... 's', + ... 'retval', + ... 'continue', + ... 'args', + ... 'display self', + ... 'display', + ... 'continue', + ... ]): + ... test_function() + > (4)__new__() + -> return object.__new__(cls) + (Pdb) s + --Return-- + > (4)__new__()-> + -> return object.__new__(cls) + (Pdb) retval + *** repr(retval) failed: AttributeError: 'A' object has no attribute 'a' *** + (Pdb) continue + > (7)__init__() + -> self.a = 1 + (Pdb) args + self = *** repr(self) failed: AttributeError: 'A' object has no attribute 'a' *** + (Pdb) display self + display self: *** repr(self) failed: AttributeError: 'A' object has no attribute 'a' *** + (Pdb) display + Currently displaying: + self: *** repr(self) failed: AttributeError: 'A' object has no attribute 'a' *** + (Pdb) continue + """ + @support.requires_subprocess() class PdbTestCase(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2023-10-09-19-09-32.gh-issue-65052.C2mRlo.rst b/Misc/NEWS.d/next/Library/2023-10-09-19-09-32.gh-issue-65052.C2mRlo.rst new file mode 100644 index 00000000000000..4739c63bb3cc9d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-09-19-09-32.gh-issue-65052.C2mRlo.rst @@ -0,0 +1 @@ +Prevent :mod:`pdb` from crashing when trying to display undisplayable objects