diff --git a/test/bytecode_2.7/05_for_try_else.pyc b/test/bytecode_2.7/05_for_try_else.pyc new file mode 100644 index 00000000..e93eff4f Binary files /dev/null and b/test/bytecode_2.7/05_for_try_else.pyc differ diff --git a/test/simple_source/bug27+/03_not_dead_code.py b/test/simple_source/bug27+/03_not_dead_code.py new file mode 100644 index 00000000..f5d167d3 --- /dev/null +++ b/test/simple_source/bug27+/03_not_dead_code.py @@ -0,0 +1,9 @@ +# Bug found in 2.7 test_itertools.py +# Bug was erroneously using reduction to unconditional_true +# A proper fix would be to use unconditional_true only when we +# can determine there is or was dead code. +from itertools import izip_longest +for args in [['abc', range(6)]]: + target = [tuple([arg[i] if i < len(arg) else None for arg in args]) + for i in range(max(map(len, args)))] + assert list(izip_longest(*args)) == target diff --git a/test/simple_source/bug27+/05_for_try_else.py b/test/simple_source/bug27+/05_for_try_else.py new file mode 100644 index 00000000..17cd4e42 --- /dev/null +++ b/test/simple_source/bug27+/05_for_try_else.py @@ -0,0 +1,20 @@ +# Bug found in 2.7 test_itertools.py +def test_iziplongest(self): + + # Having a for loop seems important + for args in ['abc']: + self.assertEqual(1, 2) + + pass # Having this seems important + + # The bug was the except jumping back + # to the beginning of this for loop + for stmt in [ + "izip_longest('abc', fv=1)", + ]: + try: + eval(stmt) + except TypeError: + pass + else: + self.fail() diff --git a/test/stdlib/runtests.sh b/test/stdlib/runtests.sh index d74d248c..4e821221 100755 --- a/test/stdlib/runtests.sh +++ b/test/stdlib/runtests.sh @@ -77,7 +77,6 @@ case $PYVERSION in [test_grammar.py]=1 # Too many stmts. Handle large stmts [test_io.py]=1 # Test takes too long to run [test_ioctl.py]=1 # Test takes too long to run - [test_itertools.py]=1 # Syntax error - look at! [test_memoryio.py]=1 # FIX [test_multiprocessing.py]=1 # On uncompyle2, taks 24 secs [test_pep352.py]=1 # ? diff --git a/uncompyle6/parsers/parse27.py b/uncompyle6/parsers/parse27.py index 5ac2355c..28815c15 100644 --- a/uncompyle6/parsers/parse27.py +++ b/uncompyle6/parsers/parse27.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2017 Rocky Bernstein +# Copyright (c) 2016-2018 Rocky Bernstein # Copyright (c) 2005 by Dan Pascu # Copyright (c) 2000-2002 by hartmut Goebel @@ -17,9 +17,11 @@ class Python27Parser(Python2Parser): list_for ::= expr for_iter store list_iter JUMP_BACK list_comp ::= BUILD_LIST_0 list_iter lc_body ::= expr LIST_APPEND + for_iter ::= GET_ITER COME_FROM FOR_ITER stmt ::= setcomp_func + # Dictionary and set comprehensions were added in Python 2.7 expr ::= dict_comp dict_comp ::= LOAD_DICTCOMP MAKE_FUNCTION_0 expr GET_ITER CALL_FUNCTION_1 @@ -53,6 +55,9 @@ class Python27Parser(Python2Parser): tryelsestmtl ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK except_handler else_suitel JUMP_BACK COME_FROM + tryelsestmtl ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK + except_handler else_suitel + except_stmt ::= except_cond2 except_suite except_cond1 ::= DUP_TOP expr COMPARE_OP @@ -60,6 +65,9 @@ class Python27Parser(Python2Parser): except_cond2 ::= DUP_TOP expr COMPARE_OP jmp_false POP_TOP store POP_TOP + + for_block ::= l_stmts_opt JUMP_BACK + """ def p_jump27(self, args): @@ -94,7 +102,10 @@ class Python27Parser(Python2Parser): # conditional_true are for conditions which always evaluate true # There is dead or non-optional remnants of the condition code though, - # and we use that to match on to reconstruct the source more accurately + # and we use that to match on to reconstruct the source more accurately. + # FIXME: we should do analysis and reduce *only* if there is dead code? + # right now we check that expr is "or". Any other nodes types? + expr ::= conditional_true conditional_true ::= expr JUMP_FORWARD expr COME_FROM @@ -159,6 +170,7 @@ class Python27Parser(Python2Parser): """) super(Python27Parser, self).customize_grammar_rules(tokens, customize) self.check_reduce['and'] = 'AST' + self.check_reduce['conditional_true'] = 'AST' return def reduce_is_invalid(self, rule, ast, tokens, first, last): @@ -174,6 +186,12 @@ class Python27Parser(Python2Parser): jmp_target = jmp_false.offset + jmp_false.attr + 3 return not (jmp_target == tokens[last].offset or tokens[last].pattr == jmp_false.pattr) + elif rule[0] == ('conditional_true'): + # FIXME: the below is a hack: we check expr for + # nodes that could have possibly been a been a Boolean. + # We should also look for the presence of dead code. + return ast[0] == 'expr' and ast[0] == 'or' + return False diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index 59d66ee0..1df1e22a 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -1104,10 +1104,6 @@ class Python30Parser(Python3Parser): def p_30(self, args): """ - # Store locals is only in Python 3.0 to 3.3 - stmt ::= store_locals - store_locals ::= LOAD_FAST STORE_LOCALS - jmp_true ::= JUMP_IF_TRUE_OR_POP POP_TOP _ifstmts_jump ::= c_stmts_opt JUMP_FORWARD POP_TOP COME_FROM """ diff --git a/uncompyle6/parsers/parse30.py b/uncompyle6/parsers/parse30.py index 0f8c3fa0..3f53284a 100644 --- a/uncompyle6/parsers/parse30.py +++ b/uncompyle6/parsers/parse30.py @@ -10,9 +10,6 @@ class Python30Parser(Python31Parser): def p_30(self, args): """ - # Store locals is only in Python 3.0 to 3.3 - stmt ::= store_locals - store_locals ::= LOAD_FAST STORE_LOCALS # FIXME: combine with parse3.2 whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK diff --git a/uncompyle6/parsers/parse33.py b/uncompyle6/parsers/parse33.py index f0c7dd62..5fe2e6f1 100644 --- a/uncompyle6/parsers/parse33.py +++ b/uncompyle6/parsers/parse33.py @@ -25,6 +25,13 @@ class Python33Parser(Python32Parser): jump_excepts come_from_except_clauses """ + def p_30to33(self, args): + """ + # Store locals is only in Python 3.0 to 3.3 + stmt ::= store_locals + store_locals ::= LOAD_FAST STORE_LOCALS + """ + def customize_grammar_rules(self, tokens, customize): self.remove_rules(""" # 3.3+ adds POP_BLOCKS