Skip to content
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

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

Closed
jneb mannequin opened this issue Mar 5, 2014 · 12 comments
Closed

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

jneb mannequin opened this issue Mar 5, 2014 · 12 comments
Labels
stdlib Python modules in the Lib dir type-feature A feature request or enhancement

Comments

@jneb
Copy link
Mannequin

jneb mannequin commented Mar 5, 2014

BPO 20853
Nosy @jneb, @bitdancer, @xdegaye, @akulakov
PRs
  • bpo-20853: 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

    @jneb
    Copy link
    Mannequin Author

    jneb mannequin commented Mar 5, 2014

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

    @jneb jneb mannequin added stdlib Python modules in the Lib dir type-feature A feature request or enhancement labels Mar 5, 2014
    @bitdancer
    Copy link
    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.

    @jneb
    Copy link
    Mannequin Author

    jneb mannequin commented Mar 12, 2014

    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.

    @jneb
    Copy link
    Mannequin Author

    jneb mannequin commented Mar 12, 2014

    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.

    @jneb
    Copy link
    Mannequin Author

    jneb mannequin commented Mar 12, 2014

    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'

    @bitdancer
    Copy link
    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)

    @jneb
    Copy link
    Mannequin Author

    jneb mannequin commented Mar 14, 2014

    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.

    @jneb
    Copy link
    Mannequin Author

    jneb mannequin commented Mar 17, 2014

    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.

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented Mar 18, 2014

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

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented May 23, 2014

    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.

    @akulakov
    Copy link
    Contributor

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

    @gaogaotiantian
    Copy link
    Member

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

    miss-islington pushed a commit to miss-islington/cpython that referenced this issue Oct 11, 2023
    …cts (pythonGH-110578)
    
    (cherry picked from commit c523ce0)
    
    Co-authored-by: Tian Gao <gaogaotiantian@hotmail.com>
    iritkatriel pushed a commit that referenced this issue Oct 11, 2023
    …ects (GH-110578) (#110734)
    
    gh-65052: Prevent pdb from crashing when trying to display objects (GH-110578)
    (cherry picked from commit c523ce0)
    
    Co-authored-by: Tian Gao <gaogaotiantian@hotmail.com>
    gaogaotiantian added a commit to gaogaotiantian/cpython that referenced this issue Oct 17, 2023
    …ay objects (pythonGH-110578)
    
    (cherry picked from commit c523ce0)
    
    Co-authored-by: Tian Gao <gaogaotiantian@hotmail.com>
    brandtbucher pushed a commit that referenced this issue Oct 18, 2023
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    stdlib Python modules in the Lib dir type-feature A feature request or enhancement
    Projects
    None yet
    Development

    No branches or pull requests

    3 participants