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
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
------------

Binary file not shown.

View File

@ -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__:

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

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