diff --git a/README.rst b/README.rst index 65b1dc97..a54722dc 100644 --- a/README.rst +++ b/README.rst @@ -12,20 +12,19 @@ Introduction *uncompyle6* translates Python bytecode back into equivalent Python source code. It accepts bytecodes from Python version 2.3 to 3.5 or -so. The code requires Python 2.6 or later and has been tested on Python -running versions 2.3-2.7, and 3.2-3.5. +so, including PyPy bytecode. Why this? --------- There were a number of decompyle, uncompile, uncompyle2, uncompyle3 -forks around. All of them come basically from the same code base, and -almost all of them no longer maintained or worked on. Only one handled -Python 3, and even there, only 3.2. This code pulls these together, -handles a wide range of bytecodes and addresses a number of open -issues in previous forks. +forks around. All of them came basically from the same code base, and +almost all of them no were no longer actively maintained. Only one +handled Python 3, and even there, only 3.2. This code pulls these +together and moves forward. It also addresses a number of open issues +in the previous forks. -What makes this different from other CPython bytecode decompilers? Its +What makes this different from other CPython bytecode decompilers?: its ability to deparse just fragments and give source-code information around a given bytecode offset. @@ -41,6 +40,13 @@ location in more detail than just a line number. It can be also used when source-code information does not exist and there is just bytecode information. +Requirements +------------ + +This project requires Python 2.6 or later, PyPy 3-2.40, or PyPy-5.0.1. +The bytecode files it can read has been tested on Python bytecodes from +versions 2.3-2.7, and 3.2-3.5 and the above-mentioned PyPy versions. + Installation ------------ diff --git a/test/bytecode_pypy2.7/03_if_elif.pyc b/test/bytecode_pypy2.7/03_if_elif.pyc new file mode 100644 index 00000000..fb3de46d Binary files /dev/null and b/test/bytecode_pypy2.7/03_if_elif.pyc differ diff --git a/test/simple_source/stmts/03_if_elif.py b/test/simple_source/stmts/03_if_elif.py index 0520d6ff..798f86b2 100644 --- a/test/simple_source/stmts/03_if_elif.py +++ b/test/simple_source/stmts/03_if_elif.py @@ -2,14 +2,19 @@ # Bug in 2.6 is having multple COME_FROMs due to the # "and" in the "if" clause if __name__: - if __file__ and name: + if __file__ and __name__: pass - elif name: + elif __name__: pass # 2.6.9 transformer.py # Bug in 2.6 is multple COME_FROMs as a result # of the "or" in the "assert" + +# In PyPy the assert is handled via PyPy's unique JUMP_IF_NOT_DEBUG +# instruction. + +# Also note that the "else: pass" is superfluous if __name__: pass elif __file__: diff --git a/uncompyle6/parsers/parse2.py b/uncompyle6/parsers/parse2.py index 16e4dcee..c688be7b 100644 --- a/uncompyle6/parsers/parse2.py +++ b/uncompyle6/parsers/parse2.py @@ -275,6 +275,14 @@ class Python2Parser(PythonParser): self.add_unique_rule("call_function ::= expr CALL_METHOD", op, v, customize) continue + elif k == 'JUMP_IF_NOT_DEBUG': + self.add_unique_rule( + "stmt ::= assert_pypy", op, v, customize) + self.add_unique_rule( + "assert_pypy ::= JUMP_IF_NOT_DEBUG assert_expr jmp_true " + "LOAD_ASSERT RAISE_VARARGS_1 COME_FROM", + op, v, customize) + continue elif op == 'BUILD_MAP': kvlist_n = "kvlist_%s" % v rule = kvlist_n + ' ::= ' + ' kv3' * v diff --git a/uncompyle6/parsers/parse27.py b/uncompyle6/parsers/parse27.py index aeede81e..8724351f 100644 --- a/uncompyle6/parsers/parse27.py +++ b/uncompyle6/parsers/parse27.py @@ -47,7 +47,10 @@ class Python27Parser(Python2Parser): def p_stmt27(self, args): """ + # assert condition assert ::= assert_expr jmp_true LOAD_ASSERT RAISE_VARARGS_1 + + # assert condition, expr assert2 ::= assert_expr jmp_true LOAD_ASSERT expr RAISE_VARARGS_2 withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index 59b93d23..83ff1c66 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -250,6 +250,10 @@ class Python3Parser(PythonParser): def p_misc3(self, args): """ try_middle ::= JUMP_FORWARD COME_FROM except_stmts END_FINALLY NOP COME_FROM + for_block ::= l_stmts + iflaststmtl ::= testexpr c_stmts_opt + iflaststmt ::= testexpr c_stmts_opt34 + c_stmts_opt34 ::= JUMP_BACK JUMP_ABSOLUTE c_stmts_opt """ def p_jump3(self, args): @@ -298,14 +302,6 @@ class Python3Parser(PythonParser): binary_subscr2 ::= expr expr DUP_TOP_TWO BINARY_SUBSCR ''' - def p_misc3(self, args): - ''' - for_block ::= l_stmts - iflaststmtl ::= testexpr c_stmts_opt - iflaststmt ::= testexpr c_stmts_opt34 - c_stmts_opt34 ::= JUMP_BACK JUMP_ABSOLUTE c_stmts_opt - ''' - @staticmethod def call_fn_name(token): """Customize CALL_FUNCTION to add the number of positional arguments""" @@ -475,6 +471,14 @@ class Python3Parser(PythonParser): self.add_unique_rule("call_function ::= expr CALL_METHOD", opname, token.attr, customize) continue + elif opname == 'JUMP_IF_NOT_DEBUG': + self.add_unique_rule( + "stmt ::= assert_pypy", opname, v, customize) + self.add_unique_rule( + "assert_pypy ::= JUMP_IF_NOT_DEBUG assert_expr jmp_true " + "LOAD_ASSERT RAISE_VARARGS_1 COME_FROM", + opname, token.attr, customize) + continue elif opname_base == 'BUILD_MAP': kvlist_n = "kvlist_%s" % token.attr if self.version >= 3.5: diff --git a/uncompyle6/scanners/scanner2.py b/uncompyle6/scanners/scanner2.py index 8f7baa82..ff4b0bcb 100755 --- a/uncompyle6/scanners/scanner2.py +++ b/uncompyle6/scanners/scanner2.py @@ -192,7 +192,7 @@ class Scanner2(scan.Scanner): opname = '%s_%d' % (opname, oparg) if op != self.opc.BUILD_SLICE: customize[opname] = oparg - elif self.is_pypy and opname == 'CALL_METHOD': + elif self.is_pypy and opname in ('CALL_METHOD', 'JUMP_IF_NOT_DEBUG'): customize[opname] = oparg elif op == self.opc.JUMP_ABSOLUTE: target = self.get_target(offset) diff --git a/uncompyle6/scanners/scanner3.py b/uncompyle6/scanners/scanner3.py index 9e645ed6..bdeb84e0 100644 --- a/uncompyle6/scanners/scanner3.py +++ b/uncompyle6/scanners/scanner3.py @@ -237,7 +237,7 @@ class Scanner3(scan.Scanner): elif op in self.varargs: pos_args = inst.argval opname = '%s_%d' % (opname, pos_args) - elif self.is_pypy and opname == 'CALL_METHOD': + elif self.is_pypy and opname in ('CALL_METHOD', 'JUMP_IF_NOT_DEBUG'): customize['CALL_METHOD'] = argval elif opname == 'UNPACK_EX': # FIXME: try with scanner and parser by diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index 971bf255..cdcb17e8 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -381,6 +381,10 @@ TABLE_DIRECT = { ####################### 'LOAD_CLASSDEREF': ( '%{pattr}', ), + ######################## + # PyPy Additions + ####################### + 'assert_pypy': ( '%|assert %c\n' , 1 ), }