From 873e90774c082a063f097663d2327bb285e8161c 2015-10-09 08:31:41 From: Min RK Date: 2015-10-09 08:31:41 Subject: [PATCH] Merge pull request #8882 from quantopian/fix-getargs BUG: Fix getargvalues call with nested genexprs. --- diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py index 25815c9..2d0a493 100644 --- a/IPython/core/tests/test_ultratb.py +++ b/IPython/core/tests/test_ultratb.py @@ -3,8 +3,10 @@ """ import io import os.path +from textwrap import dedent import unittest + from IPython.testing import tools as tt from IPython.testing.decorators import onlyif_unicode_paths from IPython.utils.syspathcontext import prepended_to_syspath @@ -89,6 +91,29 @@ class NonAsciiTest(unittest.TestCase): with tt.AssertPrints(u'дбИЖ', suppress=False): ip.run_cell('fail()') + +class NestedGenExprTestCase(unittest.TestCase): + """ + Regression test for the following issues: + https://github.com/ipython/ipython/issues/8293 + https://github.com/ipython/ipython/issues/8205 + """ + def test_nested_genexpr(self): + code = dedent( + """\ + class SpecificException(Exception): + pass + + def foo(x): + raise SpecificException("Success!") + + sum(sum(foo(x) for _ in [0]) for x in [0]) + """ + ) + with tt.AssertPrints('SpecificException: Success!', suppress=False): + ip.run_cell(code) + + indentationerror_file = """if True: zoon() """ diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 42bca4a..639635e 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/issue14611 by Stefano Taschini. 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 = ''