Handle PyPy JUMP_IF_NOT_DEBUG

Update README.rst to note PyPY and reorganize a little
This commit is contained in:
rocky 2016-07-25 09:06:13 -04:00
parent 285444e19a
commit 476eb50868
9 changed files with 50 additions and 20 deletions

View File

@ -12,20 +12,19 @@ Introduction
*uncompyle6* translates Python bytecode back into equivalent Python *uncompyle6* translates Python bytecode back into equivalent Python
source code. It accepts bytecodes from Python version 2.3 to 3.5 or 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 so, including PyPy bytecode.
running versions 2.3-2.7, and 3.2-3.5.
Why this? Why this?
--------- ---------
There were a number of decompyle, uncompile, uncompyle2, uncompyle3 There were a number of decompyle, uncompile, uncompyle2, uncompyle3
forks around. All of them come basically from the same code base, and forks around. All of them came basically from the same code base, and
almost all of them no longer maintained or worked on. Only one handled almost all of them no were no longer actively maintained. Only one
Python 3, and even there, only 3.2. This code pulls these together, handled Python 3, and even there, only 3.2. This code pulls these
handles a wide range of bytecodes and addresses a number of open together and moves forward. It also addresses a number of open issues
issues in previous forks. 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 ability to deparse just fragments and give source-code information
around a given bytecode offset. 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 when source-code information does not exist and there is just bytecode
information. 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 Installation
------------ ------------

Binary file not shown.

View File

@ -2,14 +2,19 @@
# Bug in 2.6 is having multple COME_FROMs due to the # Bug in 2.6 is having multple COME_FROMs due to the
# "and" in the "if" clause # "and" in the "if" clause
if __name__: if __name__:
if __file__ and name: if __file__ and __name__:
pass pass
elif name: elif __name__:
pass pass
# 2.6.9 transformer.py # 2.6.9 transformer.py
# Bug in 2.6 is multple COME_FROMs as a result # Bug in 2.6 is multple COME_FROMs as a result
# of the "or" in the "assert" # 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__: if __name__:
pass pass
elif __file__: elif __file__:

View File

@ -275,6 +275,14 @@ class Python2Parser(PythonParser):
self.add_unique_rule("call_function ::= expr CALL_METHOD", self.add_unique_rule("call_function ::= expr CALL_METHOD",
op, v, customize) op, v, customize)
continue 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': elif op == 'BUILD_MAP':
kvlist_n = "kvlist_%s" % v kvlist_n = "kvlist_%s" % v
rule = kvlist_n + ' ::= ' + ' kv3' * v rule = kvlist_n + ' ::= ' + ' kv3' * v

View File

@ -47,7 +47,10 @@ class Python27Parser(Python2Parser):
def p_stmt27(self, args): def p_stmt27(self, args):
""" """
# assert condition
assert ::= assert_expr jmp_true LOAD_ASSERT RAISE_VARARGS_1 assert ::= assert_expr jmp_true LOAD_ASSERT RAISE_VARARGS_1
# assert condition, expr
assert2 ::= assert_expr jmp_true LOAD_ASSERT expr RAISE_VARARGS_2 assert2 ::= assert_expr jmp_true LOAD_ASSERT expr RAISE_VARARGS_2
withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt

View File

@ -250,6 +250,10 @@ class Python3Parser(PythonParser):
def p_misc3(self, args): def p_misc3(self, args):
""" """
try_middle ::= JUMP_FORWARD COME_FROM except_stmts END_FINALLY NOP COME_FROM 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): def p_jump3(self, args):
@ -298,14 +302,6 @@ class Python3Parser(PythonParser):
binary_subscr2 ::= expr expr DUP_TOP_TWO BINARY_SUBSCR 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 @staticmethod
def call_fn_name(token): def call_fn_name(token):
"""Customize CALL_FUNCTION to add the number of positional arguments""" """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", self.add_unique_rule("call_function ::= expr CALL_METHOD",
opname, token.attr, customize) opname, token.attr, customize)
continue 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': elif opname_base == 'BUILD_MAP':
kvlist_n = "kvlist_%s" % token.attr kvlist_n = "kvlist_%s" % token.attr
if self.version >= 3.5: if self.version >= 3.5:

View File

@ -192,7 +192,7 @@ class Scanner2(scan.Scanner):
opname = '%s_%d' % (opname, oparg) opname = '%s_%d' % (opname, oparg)
if op != self.opc.BUILD_SLICE: if op != self.opc.BUILD_SLICE:
customize[opname] = oparg 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 customize[opname] = oparg
elif op == self.opc.JUMP_ABSOLUTE: elif op == self.opc.JUMP_ABSOLUTE:
target = self.get_target(offset) target = self.get_target(offset)

View File

@ -237,7 +237,7 @@ class Scanner3(scan.Scanner):
elif op in self.varargs: elif op in self.varargs:
pos_args = inst.argval pos_args = inst.argval
opname = '%s_%d' % (opname, pos_args) 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 customize['CALL_METHOD'] = argval
elif opname == 'UNPACK_EX': elif opname == 'UNPACK_EX':
# FIXME: try with scanner and parser by # FIXME: try with scanner and parser by

View File

@ -381,6 +381,10 @@ TABLE_DIRECT = {
####################### #######################
'LOAD_CLASSDEREF': ( '%{pattr}', ), 'LOAD_CLASSDEREF': ( '%{pattr}', ),
########################
# PyPy Additions
#######################
'assert_pypy': ( '%|assert %c\n' , 1 ),
} }