This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: pdb "args" crashes when an arg is not printable
Type: enhancement Stage: patch review
Components: Library (Lib) Versions: Python 3.3, Python 2.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: andrei.avk, jneb, r.david.murray, xdegaye
Priority: normal Keywords: patch

Created on 2014-03-05 10:45 by jneb, last changed 2022-04-11 14:57 by admin.

Files
File name Uploaded Description Edit
pdb.patch jneb, 2014-03-12 07:58 attempt to patch pdb.py
pdb.patch jneb, 2014-03-12 08:22 Simple patch to pdb to allow objects with bad __repr__
safe_repr.py xdegaye, 2014-05-23 15:08
Pull Requests
URL Status Linked Edit
PR 28400 open andrei.avk, 2021-09-17 02:42
Messages (11)
msg212759 - (view) Author: Jurjen N.E. Bos (jneb) * Date: 2014-03-05 10:45
The "args" command in pdb crashes when an argument cannot be printed.
Fortunately, this is easy to fix.

For version 3.3.3:
In function Pdb.do_args (lib/pdb.py, line 1120)
Change line 1131
  self.message('%s = %r' % (name, dict[name]))
to
  try: r = repr(dict[name])
  except: r = "(Cannot print object)"
  self.message('%s = %s' % (name, r))
msg212760 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-03-05 12:53
Why is the object not printable?  Can you provide a reproducer?

Also, the bare except is buggy, it would catch things like KeyboardInterrupt that should not be caught there.  'except Exception' would be appropriate in this case.
msg213212 - (view) Author: Jurjen N.E. Bos (jneb) * Date: 2014-03-12 07:58
Thanks for your reaction.
The object is not printable, since I was debugging an __init__ of an object, and some fields where being initialized:
class foo:
   def __init__(self):
      foo.bar = "hello"
   def repr(self): return foo.bar
I tried to make a useable patch file (with neater layout and using Exeption), see attach.
msg213214 - (view) Author: Jurjen N.E. Bos (jneb) * Date: 2014-03-12 08:16
Oops. Here the correct example:
>>> class foo:
...   def __init__(self):
...     foo.bar = "hello"
...   def __repr__(self): return foo.bar
...
>>> pdb.runcall(foo)
> <stdin>(3)__init__()
(Pdb) a
Traceback (most recent call last):
  File ".\pdb.py", line 1132, in do_args
    self.message('%s = %r' % (name, dict[name]))
  File "<stdin>", line 4, in __repr__
AttributeError: type object 'foo' has no attribute 'bar'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".\pdb.py", line 1580, in runcall
    return Pdb().runcall(*args, **kwds)
  File "C:\Python33\lib\bdb.py", line 439, in runcall
    res = func(*args, **kwds)
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 3, in __init__
  File "C:\Python33\lib\bdb.py", line 47, in trace_dispatch
    return self.dispatch_line(frame)
  File "C:\Python33\lib\bdb.py", line 65, in dispatch_line
    self.user_line(frame)
  File ".\pdb.py", line 266, in user_line
    self.interaction(frame, None)
  File ".\pdb.py", line 345, in interaction
    self._cmdloop()
  File ".\pdb.py", line 318, in _cmdloop
    self.cmdloop()
  File "C:\Python33\lib\cmd.py", line 138, in cmdloop
    stop = self.onecmd(line)
  File ".\pdb.py", line 411, in onecmd
    return cmd.Cmd.onecmd(self, line)
  File "C:\Python33\lib\cmd.py", line 217, in onecmd
    return func(arg)
  File ".\pdb.py", line 1134, in do_args
    self.message('%s = *** repr failed: %s ***' % (name,))
TypeError: not enough arguments for format string

At least, I expect pdb to not crash, but a clearer error (as in the patch) is nice to have.
msg213217 - (view) Author: Jurjen N.E. Bos (jneb) * Date: 2014-03-12 08:22
I am not good at this. Sorry for the mess.
Here is a good example, and a good patch:
>>> class foo:
...   def __init__(self):
...     foo.bar = "hello"
...   def __repr__(self): return foo.bar
...
>>> pdb.runcall(foo)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'pdb' is not defined
>>> import pdb
>>> pdb.runcall(foo)
> <stdin>(3)__init__()
(Pdb) a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python33\lib\pdb.py", line 1577, in runcall
    return Pdb().runcall(*args, **kwds)
  File "C:\Python33\lib\bdb.py", line 439, in runcall
    res = func(*args, **kwds)
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 3, in __init__
  File "C:\Python33\lib\bdb.py", line 47, in trace_dispatch
    return self.dispatch_line(frame)
  File "C:\Python33\lib\bdb.py", line 65, in dispatch_line
    self.user_line(frame)
  File "C:\Python33\lib\pdb.py", line 266, in user_line
    self.interaction(frame, None)
  File "C:\Python33\lib\pdb.py", line 345, in interaction
    self._cmdloop()
  File "C:\Python33\lib\pdb.py", line 318, in _cmdloop
    self.cmdloop()
  File "C:\Python33\lib\cmd.py", line 138, in cmdloop
    stop = self.onecmd(line)
  File "C:\Python33\lib\pdb.py", line 411, in onecmd
    return cmd.Cmd.onecmd(self, line)
  File "C:\Python33\lib\cmd.py", line 217, in onecmd
    return func(arg)
  File "C:\Python33\lib\pdb.py", line 1131, in do_args
    self.message('%s = %r' % (name, dict[name]))
  File "<stdin>", line 4, in __repr__
AttributeError: type object 'foo' has no attribute 'bar'
msg213273 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-03-12 18:01
There is at least one other place (do_break) where this same problem could crop up.  Unittest handles this by having a 'safe_repr' function.  pdb doesn't need the same function unittest does, but it could do something similar, and then use %s and this function in the places where it currently uses repr to print an arbitrary object:

    def safe_repr(obj):
        try:
            return repr(obj)
        except Exception:
            return object.__repr__(obj)
msg213593 - (view) Author: Jurjen N.E. Bos (jneb) * Date: 2014-03-14 21:51
Maybe we could use Pdb._getval_except(arg, frame=None) in the routine do_args.
If I understand the code, do_args does quite some work to get the value of name in the context of the current frame, maybe just calling
self._getval_except(name, frame=self.curframe)
plus or minus some code would do the job?
I guess the code would actually become shorter...
I'll try to figure it out.
msg213841 - (view) Author: Jurjen N.E. Bos (jneb) * Date: 2014-03-17 08:38
I did figure it out.
It almost works, except when a argument lost its value, and the same name exists in the global context.
To be more specific: I simplified do_args to the following code
(that obviously ugly by explicitly evaluating repr in context, but
that is not the point)
    def do_args(self, arg):
        """a(rgs)
        Print the argument list of the current function.
        Modified by Jurjen
        """
        co = self.curframe.f_code
        n = co.co_argcount
        if co.co_flags & 4: n = n+1
        if co.co_flags & 8: n = n+1
        for i in range(n):
            name = co.co_varnames[i]
            expr = 'repr(%s)' % (name,)
            self.message('%s = %s' % (name, self._getval_except(expr)))

At it works perfectly, except for this little surprise:
>>> bar = "BAR"
>>> def foo(bar):
...    del bar
...    return 5
>>> pdb.runcall(f, 10)
> <stdin>(2)f()
->    del bar
(Pdb) a
bar = 5
(Pdb) n
> <stdin>(3)f()
-> return 5
(Pdb) a
bar = 'BAR'      ##### Huh? Expected undefined

I'll leave it to the experts to patch this in proper Python coding style.
So, the conclusion is we need a way to safely evaluate the call to repr() in context, with self.curframe_locals[co.co_varnames[i]] as argument.

I did not find a good supporting routine for that elsewhere in pdb.
msg213987 - (view) Author: Xavier de Gaye (xdegaye) * (Python triager) Date: 2014-03-18 16:40
> There is at least one other place (do_break) where this same problem could crop up.

Also in do_retval. And the exception is silently ignored in do_p and do_pp when repr() fails, which is not correct.

A solution could be to have a message_safe method to be used in such cases. For example, substitute in do_args:

    self.message('%s = %r' % (name, dict[name]))
with:
    self.message_safe('%s = %r', name, dict[name])

def message_safe(self, fmt, *args):
    try:
        print(fmt % args, file=self.stdout)
    except Exception:
        exc_info = sys.exc_info()[:2]
        self.error(traceback.format_exception_only(*exc_info)[-1].strip())
msg218975 - (view) Author: Xavier de Gaye (xdegaye) * (Python triager) Date: 2014-05-23 15:08
Commands that silently fail when an object is not printable: p, pp
Commands that crash when an object is not printable:
    args, retval
    Python 3: display
    Python 2: on a 'return' trace event when the return value is not printable

The attached script illustrates all these cases.
msg401999 - (view) Author: Andrei Kulakov (andrei.avk) * (Python triager) Date: 2021-09-17 02:50
p/pp commands were fixed in this commit:
https://github.com/python/cpython/commit/6544b2532df
History
Date User Action Args
2022-04-11 14:57:59adminsetgithub: 65052
2021-09-17 02:50:21andrei.avksetmessages: + msg401999
2021-09-17 02:42:36andrei.avksetnosy: + andrei.avk
pull_requests: + pull_request26814
2021-09-12 15:06:19andrei.avklinkissue42119 superseder
2014-05-23 15:08:10xdegayesetfiles: + safe_repr.py

messages: + msg218975
2014-05-09 12:55:31ezio.melottisetstage: patch review
2014-03-18 16:40:04xdegayesetnosy: + xdegaye
messages: + msg213987
2014-03-17 08:38:51jnebsetmessages: + msg213841
2014-03-14 21:51:15jnebsetmessages: + msg213593
2014-03-12 18:01:53r.david.murraysetmessages: + msg213273
2014-03-12 08:22:57jnebsetfiles: + pdb.patch

messages: + msg213217
2014-03-12 08:16:21jnebsetmessages: + msg213214
2014-03-12 07:58:48jnebsetfiles: + pdb.patch
keywords: + patch
messages: + msg213212
2014-03-05 12:53:57r.david.murraysetnosy: + r.david.murray
messages: + msg212760
2014-03-05 10:45:57jnebcreate