From e4c2b5daf5bae8d335167806c210439295c189f8 2015-10-08 00:16:55 From: Scott Sanderson Date: 2015-10-08 00:16:55 Subject: [PATCH] BUG: Fix getargvalues call with nested genexprs. Fixes https://github.com/ipython/ipython/issues/8293 and https://github.com/ipython/ipython/issues/8205. --- diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 42bca4a..d247508 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -83,6 +83,7 @@ Inheritance diagram: from __future__ import unicode_literals from __future__ import print_function +import dis import inspect import keyword import linecache @@ -222,21 +223,98 @@ def findsource(object): raise IOError('could not find code object') +# This is a patched version of inspect.getargs that applies the (unmerged) +# patch for http://bugs.python.org/file25269/issue_14611. This fixes +# https://github.com/ipython/ipython/issues/8205 and +# https://github.com/ipython/ipython/issues/8293 +def getargs(co): + """Get information about the arguments accepted by a code object. + + Three things are returned: (args, varargs, varkw), where 'args' is + a list of argument names (possibly containing nested lists), and + 'varargs' and 'varkw' are the names of the * and ** arguments or None.""" + if not iscode(co): + raise TypeError('{!r} is not a code object'.format(co)) + + nargs = co.co_argcount + names = co.co_varnames + args = list(names[:nargs]) + step = 0 + + # The following acrobatics are for anonymous (tuple) arguments. + for i in range(nargs): + if args[i][:1] in ('', '.'): + stack, remain, count = [], [], [] + while step < len(co.co_code): + op = ord(co.co_code[step]) + step = step + 1 + if op >= dis.HAVE_ARGUMENT: + opname = dis.opname[op] + value = ord(co.co_code[step]) + ord(co.co_code[step+1])*256 + step = step + 2 + if opname in ('UNPACK_TUPLE', 'UNPACK_SEQUENCE'): + remain.append(value) + count.append(value) + elif opname in ('STORE_FAST', 'STORE_DEREF'): + if op in dis.haslocal: + stack.append(co.co_varnames[value]) + elif op in dis.hasfree: + stack.append((co.co_cellvars + co.co_freevars)[value]) + # Special case for sublists of length 1: def foo((bar)) + # doesn't generate the UNPACK_TUPLE bytecode, so if + # `remain` is empty here, we have such a sublist. + if not remain: + stack[0] = [stack[0]] + break + else: + remain[-1] = remain[-1] - 1 + while remain[-1] == 0: + remain.pop() + size = count.pop() + stack[-size:] = [stack[-size:]] + if not remain: break + remain[-1] = remain[-1] - 1 + if not remain: break + args[i] = stack[0] + + varargs = None + if co.co_flags & inspect.CO_VARARGS: + varargs = co.co_varnames[nargs] + nargs = nargs + 1 + varkw = None + if co.co_flags & inspect.CO_VARKEYWORDS: + varkw = co.co_varnames[nargs] + return inspect.Arguments(args, varargs, varkw) + + # Monkeypatch inspect to apply our bugfix. def with_patch_inspect(f): """decorator for monkeypatching inspect.findsource""" def wrapped(*args, **kwargs): save_findsource = inspect.findsource + save_getargs = inspect.getargs inspect.findsource = findsource + inspect.getargs = getargs try: return f(*args, **kwargs) finally: inspect.findsource = save_findsource + inspect.getargs = save_getargs return wrapped +if py3compat.PY3: + fixed_getargvalues = inspect.getargvalues +else: + # Fixes for https://github.com/ipython/ipython/issues/8293 + # and https://github.com/ipython/ipython/issues/8205. + # The relevant bug is caused by failure to correctly handle anonymous tuple + # unpacking, which only exists in Python 2. + fixed_getargvalues = with_patch_inspect(inspect.getargvalues) + + def fix_frame_records_filenames(records): """Try to fix the filenames in each record from inspect.getinnerframes(). @@ -744,7 +822,7 @@ class VerboseTB(TBTools): file = py3compat.cast_unicode(file, util_path.fs_encoding) link = tpl_link % file - args, varargs, varkw, locals = inspect.getargvalues(frame) + args, varargs, varkw, locals = fixed_getargvalues(frame) if func == '?': call = ''