Skip to content

pdb "args" crashes when an arg is not printable #65052

Closed

Description

@jneb
mannequin
BPO 20853
Nosy @jneb, @bitdancer, @xdegaye, @akulakov
PRs
  • gh-65052: fixed PDB's args and retval commands to handle objects with broken repr() #28400
  • Files
  • pdb.patch: attempt to patch pdb.py
  • pdb.patch: Simple patch to pdb to allow objects with bad repr
  • safe_repr.py
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2014-03-05.10:45:57.026>
    labels = ['type-feature', 'library']
    title = 'pdb "args" crashes when an arg is not printable'
    updated_at = <Date 2021-09-17.02:50:21.987>
    user = 'https://github.com/jneb'

    bugs.python.org fields:

    activity = <Date 2021-09-17.02:50:21.987>
    actor = 'andrei.avk'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2014-03-05.10:45:57.026>
    creator = 'jneb'
    dependencies = []
    files = ['34369', '34370', '35326']
    hgrepos = []
    issue_num = 20853
    keywords = ['patch']
    message_count = 11.0
    messages = ['212759', '212760', '213212', '213214', '213217', '213273', '213593', '213841', '213987', '218975', '401999']
    nosy_count = 4.0
    nosy_names = ['jneb', 'r.david.murray', 'xdegaye', 'andrei.avk']
    pr_nums = ['28400']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue20853'
    versions = ['Python 2.7', 'Python 3.3']

    Linked PRs

    Activity

    1. jneb commented on Mar 5, 2014

      jnebmannequin
      MannequinAuthor

      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))

    2. added
      stdlibStandard Library Python modules in the Lib/ directory
      type-featureA feature request or enhancement
      on Mar 5, 2014
    3. bitdancer commented on Mar 5, 2014

      @bitdancer
      Member

      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.

    4. jneb commented on Mar 12, 2014

      jnebmannequin
      MannequinAuthor

      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.

    5. jneb commented on Mar 12, 2014

      jnebmannequin
      MannequinAuthor
      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.

    6. jneb commented on Mar 12, 2014

      jnebmannequin
      MannequinAuthor
      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'
    7. bitdancer commented on Mar 12, 2014

      @bitdancer
      Member

      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)
    8. jneb commented on Mar 14, 2014

      jnebmannequin
      MannequinAuthor

      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.

    9. jneb commented on Mar 17, 2014

      jnebmannequin
      MannequinAuthor

      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.

    10. xdegaye commented on Mar 18, 2014

      xdegayemannequin
      Mannequin

      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())
    11. xdegaye commented on May 23, 2014

      xdegayemannequin
      Mannequin

      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.

    12. akulakov commented on Sep 17, 2021

      @akulakov
      Contributor

      p/pp commands were fixed in this commit:
      6544b2532df

    13. transferred this issue fromon Apr 10, 2022
    14. added a commit that references this issue on Oct 11, 2023
    15. gaogaotiantian commented on Oct 11, 2023

      @gaogaotiantian
      Member

      args, retval and display fixed in #110578, close issue.

    16. added a commit that references this issue on Oct 11, 2023
    17. added a commit that references this issue on Oct 11, 2023
    18. added a commit that references this issue on Oct 17, 2023
    19. added a commit that references this issue on Oct 18, 2023
    20. added a commit that references this issue on Sep 2, 2024
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      stdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions