From dcad6cf6ce785519986ff4849c482894fd8dae23 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 10 Mar 2019 05:59:15 -0400 Subject: [PATCH] Fix if return boundary in 3.6+ Fixes #209 --- __pkginfo__.py | 2 +- .../bytecode_3.6_run/00_return_return_bug.pyc | Bin 0 -> 339 bytes .../bug36/00_return_return_bug.py | 10 +++++++ tox.ini | 2 +- uncompyle6/disas.py | 27 +++++++++--------- uncompyle6/parser.py | 5 +++- uncompyle6/parsers/parse3.py | 3 +- uncompyle6/parsers/parse36.py | 4 +++ uncompyle6/scanner.py | 7 +++-- uncompyle6/scanners/scanner3.py | 4 +++ 10 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 test/bytecode_3.6_run/00_return_return_bug.pyc create mode 100644 test/simple_source/bug36/00_return_return_bug.py diff --git a/__pkginfo__.py b/__pkginfo__.py index acbf0578..aae73b6c 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -57,7 +57,7 @@ entry_points = { ]} ftp_url = None install_requires = ['spark-parser >= 1.8.7, < 1.9.0', - 'xdis >= 3.8.9, < 3.9.0'] + 'xdis >= 3.8.10, < 3.9.0'] license = 'GPL3' mailing_list = 'python-debugger@googlegroups.com' diff --git a/test/bytecode_3.6_run/00_return_return_bug.pyc b/test/bytecode_3.6_run/00_return_return_bug.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94fd963d501f107522b3b19898cbf19636b45103 GIT binary patch literal 339 zcmZWj!Ab)`3{7Td%PLmt%`Xs_iY*j8i74X1OE0~Z!lKQnrPEHy>>>qE+Mnq!&Ba6i z!jqY;qR>EI-g_jE^qS56`_I+r1OYbiuaxj>X@4d_K)8Y-7$Eh5luVUBF7X{jYtdTt zY1EVsAa9H%tFAa>&aJd>3A{w^$ENdGJ@+m;-EP6Ata$!A#6Djv!_kf#gM32(Fhh+TDaR){e#wbI mKUcLK4sX0STu$8lj@j|w6I+(bZpbcYjw)-e>fi`j+QM(QXiH20 literal 0 HcmV?d00001 diff --git a/test/simple_source/bug36/00_return_return_bug.py b/test/simple_source/bug36/00_return_return_bug.py new file mode 100644 index 00000000..a7c48547 --- /dev/null +++ b/test/simple_source/bug36/00_return_return_bug.py @@ -0,0 +1,10 @@ +# Bug in 3.6 and above. +#Not detecting 2nd return is outside of +# if/then. Fix was to ensure COME_FROM +def return_return_bug(foo): + if foo =='say_hello': + return "hello" + return "world" + +assert return_return_bug('say_hello') == 'hello' +assert return_return_bug('world') == 'world' diff --git a/tox.ini b/tox.ini index 861da45e..454a9843 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ [flake8] exclude = .tox,./build,./trepan/processor/command/tmp filename = *.py -ignore = C901,E113,E121,E122,E123,E124,E125,E126,E127,E128,E129,E201,E202,E203,E221,E222,E225,E226,E241,E242,E251,E261,E271,E272,E302,E401,E501,F401,E701,E702 +ignore = C901,E113,E121,E122,E123,E124,E125,E126,E127,E128,E129,E201,E202,E203,E221,E222,E225,E226,E241,E242,E251,E261,E271,E272,E302,E401,E402,E501,F401,E701,E702 [tox] envlist = py27, py34, pypy diff --git a/uncompyle6/disas.py b/uncompyle6/disas.py index cc11d035..44b4375d 100644 --- a/uncompyle6/disas.py +++ b/uncompyle6/disas.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2016, 2818 by Rocky Bernstein +# Copyright (c) 2015-2016, 2818-2019 by Rocky Bernstein # Copyright (c) 2005 by Dan Pascu # Copyright (c) 2000-2002 by hartmut Goebel # Copyright (c) 1999 John Aycock @@ -77,18 +77,18 @@ def disco_loop(disasm, queue, real_out): pass pass -def disassemble_fp(fp, outstream=None): - """ - disassemble Python byte-code from an open file - """ - (version, timestamp, magic_int, co, is_pypy, - source_size) = load_from_fp(fp) - if type(co) == list: - for con in co: - disco(version, con, outstream) - else: - disco(version, co, outstream, is_pypy=is_pypy) - co = None +# def disassemble_fp(fp, outstream=None): +# """ +# disassemble Python byte-code from an open file +# """ +# (version, timestamp, magic_int, co, is_pypy, +# source_size) = load_from_fp(fp) +# if type(co) == list: +# for con in co: +# disco(version, con, outstream) +# else: +# disco(version, co, outstream, is_pypy=is_pypy) +# co = None def disassemble_file(filename, outstream=None): """ @@ -120,5 +120,6 @@ def _test(): fn = sys.argv[1] disassemble_file(fn) + if __name__ == "__main__": _test() diff --git a/uncompyle6/parser.py b/uncompyle6/parser.py index 378c777a..08d701c0 100644 --- a/uncompyle6/parser.py +++ b/uncompyle6/parser.py @@ -37,7 +37,9 @@ class ParserError(Exception): return "Parse error at or near `%r' instruction at offset %s\n" % \ (self.token, self.offset) -nop_func = lambda self, args: None + +def nop_func(self, args): + return None class PythonParser(GenericASTBuilder): @@ -792,6 +794,7 @@ def python_parser(version, co, out=sys.stdout, showasm=False, p = get_python_parser(version, parser_debug) return parse(p, tokens, customize) + if __name__ == '__main__': def parse_test(co): from uncompyle6 import PYTHON_VERSION, IS_PYPY diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index 838ba02a..eacabdcd 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -309,7 +309,7 @@ class Python3Parser(PythonParser): # FIXME: Common with 2.7 ret_and ::= expr JUMP_IF_FALSE_OR_POP ret_expr_or_cond COME_FROM ret_or ::= expr JUMP_IF_TRUE_OR_POP ret_expr_or_cond COME_FROM - ret_cond ::= expr POP_JUMP_IF_FALSE expr RETURN_END_IF ret_expr_or_cond + ret_cond ::= expr POP_JUMP_IF_FALSE expr RETURN_END_IF COME_FROM ret_expr_or_cond or ::= expr JUMP_IF_TRUE_OR_POP expr COME_FROM and ::= expr JUMP_IF_FALSE_OR_POP expr COME_FROM @@ -583,7 +583,6 @@ class Python3Parser(PythonParser): stmt ::= assign2_pypy assign3_pypy ::= expr expr expr store store store assign2_pypy ::= expr expr store store - return_if_lambda ::= RETURN_END_IF_LAMBDA stmt ::= conditional_lambda stmt ::= conditional_not_lambda conditional_lambda ::= expr jmp_false expr return_if_lambda diff --git a/uncompyle6/parsers/parse36.py b/uncompyle6/parsers/parse36.py index 08752445..b27ef383 100644 --- a/uncompyle6/parsers/parse36.py +++ b/uncompyle6/parsers/parse36.py @@ -46,6 +46,10 @@ class Python36Parser(Python35Parser): # RETURN_VALUE is meant. Specifcally this can happen in # ifelsestmt -> ...else_suite _. suite_stmts... (last) stmt return ::= ret_expr RETURN_END_IF + return ::= ret_expr RETURN_VALUE COME_FROM + return_stmt_lambda ::= ret_expr RETURN_VALUE_LAMBDA COME_FROM + return_if_lambda ::= RETURN_END_IF_LAMBDA COME_FROM + # A COME_FROM is dropped off because of JUMP-to-JUMP optimization and ::= expr jmp_false expr diff --git a/uncompyle6/scanner.py b/uncompyle6/scanner.py index 9df4c08e..c9318d8b 100755 --- a/uncompyle6/scanner.py +++ b/uncompyle6/scanner.py @@ -351,9 +351,9 @@ class Scanner(object): return result - # FIXME: this is broken on 3.6+. Replace remaining (2.x-based) calls # with inst_matches + def all_instr(self, start, end, instr, target=None, include_beyond_target=False): """ Find all `instr` in the block from start to end. @@ -423,8 +423,8 @@ class Scanner(object): last_was_extarg = False n = len(instructions) for i, inst in enumerate(instructions): - if (inst.opname == 'EXTENDED_ARG' and - i+1 < n and instructions[i+1].opname != 'MAKE_FUNCTION'): + if (inst.opname == 'EXTENDED_ARG' + and i+1 < n and instructions[i+1].opname != 'MAKE_FUNCTION'): last_was_extarg = True starts_line = inst.starts_line is_jump_target = inst.is_jump_target @@ -527,6 +527,7 @@ def get_scanner(version, is_pypy=False, show_asm=None): raise RuntimeError("Unsupported Python version %s" % version) return scanner + if __name__ == "__main__": import inspect, uncompyle6 co = inspect.currentframe().f_code diff --git a/uncompyle6/scanners/scanner3.py b/uncompyle6/scanners/scanner3.py index 8ab1f6c0..c469e810 100644 --- a/uncompyle6/scanners/scanner3.py +++ b/uncompyle6/scanners/scanner3.py @@ -920,6 +920,10 @@ class Scanner3(Scanner): return pass pass + + if self.version >= 3.4: + self.fixed_jumps[offset] = rtarget + if code[pre_rtarget] == self.opc.RETURN_VALUE: # If we are at some sort of POP_JUMP_IF and the instruction before was # COMPARE_OP exception-match, then pre_rtarget is not an end_if