Compare commits

...

50 Commits

Author SHA1 Message Date
rocky
0e5eb954b2 Adminstrivia 2019-04-19 06:01:06 -04:00
rocky
7d9286b353 Get ready for release 3.3.1 2019-04-19 05:51:05 -04:00
rocky
0de99e5d44 Scale back "try" vs. "tryelse" reduction test on 3.6+ 2019-04-18 16:45:44 -04:00
rocky
52af2ba32a 3.6+ lambda parameter handling 2019-04-18 14:21:52 -04:00
rocky
bd0db6c539 Extend annotate test to 3.7 2019-04-18 09:47:54 -04:00
rocky
8663b4ca52 Fix bugs caused by last commit 2019-04-18 07:31:16 -04:00
rocky
b2dd58a85e Hacky attemp to add more 3.x annotate information in 2019-04-18 02:26:50 -04:00
rocky
97cb193a71 3.7 chained comparison grammar 2019-04-17 23:41:41 -04:00
rocky
e6e60cb49d 3.6 Chained compare 2019-04-17 15:44:33 -04:00
rocky
2ea8c3b1b1 3.7 and 3.8 chained compare fixups 2019-04-16 10:19:16 -04:00
rocky
701d2af54e Improve Python 2.7 generator handling 2019-04-15 23:14:44 -04:00
rocky
8a4189bc0e Python 2.6-2.7ish generator handling 2019-04-15 20:32:15 -04:00
rocky
0c4ab699b5 3.4+ while handling with returns ...
these while loops don't have a JUMP_BACK in them
2019-04-15 12:03:11 -04:00
rocky
8e11c53064 More cleanup from recent refactoring 2019-04-15 08:18:31 -04:00
rocky
b4c66d4307 Was missing some 3.7 and 3.7 semantic actions...
Possibly some as a result of the last refactor?
2019-04-15 08:11:31 -04:00
rocky
53968e535f Split up version-specific semantic action code more 2019-04-14 21:47:16 -04:00
rocky
d2381fbe11 Update dates and version numbers 2019-04-14 19:54:53 -04:00
rocky
d413ebe0e1 Split out semantic actions per version ...
In version 3.5..3.8 there are quite hefty changes.
2019-04-14 19:25:56 -04:00
rocky
f96522e18e Add 3.8 try else 2019-04-14 19:01:33 -04:00
rocky
50d50af2ee Doc typo 2019-04-14 07:45:06 -04:00
rocky
4dc2897cdc One last new item update 2019-04-14 07:10:30 -04:00
rocky
47fb80494d Get ready for release 3.3.0 2019-04-14 07:03:07 -04:00
rocky
830e19e3e6 Get ready for release 3.3.0 2019-04-14 06:59:49 -04:00
rocky
c5cfd36a61 Start 3.8 async for/else 2019-04-14 06:54:08 -04:00
rocky
9b550b9dda PEP E225 with a nod to Deepcommit 2019-04-14 06:11:16 -04:00
rocky
400943bb6a 3.8 async for/with...
More is needed though.
2019-04-13 22:22:37 -04:00
rocky
f89ba40147 Adjust while True grammar rule 2019-04-13 20:46:13 -04:00
rocky
32c611a315 Adjust 3.8 while-stmt rules 2019-04-13 20:35:26 -04:00
rocky
5d91e96358 3.6-3.8 "async for" handling...
And add an if_stmt rule for 3.8. My want to extend it back to
other versions.
2019-04-13 18:31:40 -04:00
rocky
44edf1d7db 3.8 try/except handling - again (and more to come) 2019-04-12 03:20:13 -04:00
rocky
a891aa0706 More 3.8 try blocks 2019-04-11 16:42:28 -04:00
rocky
5e1340a2fc 3.8 exception handling 2019-04-11 12:09:24 -04:00
rocky
94eff282f8 3.8 try/except 2019-04-11 07:44:32 -04:00
rocky
7f65a8a6dd 3.8 SETUP_EXCEPT removal workaround; reinstate option -c | --compile 2019-04-11 07:19:35 -04:00
rocky
cfe7feed4d Fix 3.8 pytests 2019-04-10 22:58:15 -04:00
rocky
b3a20896b2 Remove dup pypy test 2019-04-10 12:35:26 -04:00
R. Bernstein
1425476018 Merge pull request #222 from rocky/python-3.8
Python 3.8
2019-04-10 12:00:03 -04:00
rocky
59c77f103d More self-checking run tests 2019-04-10 11:49:27 -04:00
rocky
726045a05e Basic 3.8+ "for" loop handling...
More Makefile mangling
2019-04-10 11:26:58 -04:00
rocky
49e354375e More run tests 2019-04-10 11:05:46 -04:00
rocky
f3d86e0708 Bang on Python 3.8 2019-04-10 07:22:43 -04:00
rocky
820283827f 3.8 "for" block ...
pysource: Tag older semantics for blocks with "expr" and "for_block"
2019-04-10 06:00:16 -04:00
rocky
8b65cc7275 Small changes - bump required xdis version 2019-04-09 21:45:28 -04:00
rocky
1e47f47527 Allow for newer xdis 2019-04-05 16:30:28 -04:00
rocky
19a95be3ef WIP - more 3.8 grammar stuff 2019-03-30 00:27:48 -04:00
rocky
5a6550b353 Administrivia: require xdis 3.9.1 or greater 2019-03-28 20:56:49 -04:00
rocky
82fb9426af [WIP] - move forward a tad on Python 3.8 2019-03-28 12:10:08 -04:00
rocky
98b91db8e6 Another 3.6->3.7 typo (in comment this time) 2019-03-28 11:22:07 -04:00
rocky
ce3f815b08 opcode import typo 2019-03-28 11:16:06 -04:00
rocky
c447b9cfa9 Note use of releases 2019-03-26 12:41:12 -04:00
127 changed files with 1889 additions and 987 deletions

View File

@@ -40,6 +40,9 @@ check-3.0 check-3.1 check-3.2 check-3.6:
check-3.7: pytest
$(MAKE) -C test check
check-3.8:
$(MAKE) -C test check
#:PyPy 2.6.1 PyPy 5.0.1, or PyPy 5.8.0-beta0
# Skip for now
2.6 5.0 5.3 5.6 5.8:

21
NEWS.md
View File

@@ -1,3 +1,24 @@
3.3.1 2019-04-19 Good Friday
==========================
Lots of decomplation bugs, especially in the 3.x series fixed. Don't worry though, many more remain.
* Add annotation return values in 3.6+
* Fix 3.6+ lambda parameter handling decompilation
* Fix 3.7+ chained comparision decompilation
* split out semantic-action customization into more separate files
* Add 3.8 try/else
* Fix 2.7 generator decompilation
* Fix some parser failures fixes in 3.4+ using test_pyenvlib
* Add more run tests
3.3.0 2019-43-14 Holy Week
==========================
* First cut at Python 3.8 (many bugs remain)
* Reinstate -c | --compile (compile before disassembly) option
* The usual smattering of bug and doc fixes
3.2.6 2019-03-23 Mueller Report
=======================================

View File

@@ -12,7 +12,7 @@ Introduction
*uncompyle6* translates Python bytecode back into equivalent Python
source code. It accepts bytecodes from Python version 1.3 to version
3.7, spanning over 22 years of Python releases. We include Dropbox's
3.8, spanning over 24 years of Python releases. We include Dropbox's
Python 2.5 bytecode and some PyPy bytecode.
Why this?
@@ -76,7 +76,7 @@ Requirements
The code here can be run on Python versions 2.6 or later, PyPy 3-2.4,
or PyPy-5.0.1. Python versions 2.4-2.7 are supported in the
python-2.4 branch. The bytecode files it can read have been tested on
Python bytecodes from versions 1.4, 2.1-2.7, and 3.0-3.6 and the
Python bytecodes from versions 1.4, 2.1-2.7, and 3.0-3.8 and the
above-mentioned PyPy versions.
Installation

View File

@@ -23,7 +23,7 @@
# Things that change more often go here.
copyright = """
Copyright (C) 2015-2018 Rocky Bernstein <rb@dustyfeet.com>.
Copyright (C) 2015-2019 Rocky Bernstein <rb@dustyfeet.com>.
"""
classifiers = ['Development Status :: 5 - Production/Stable',
@@ -43,6 +43,7 @@ classifiers = ['Development Status :: 5 - Production/Stable',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Software Development :: Debuggers',
'Topic :: Software Development :: Libraries :: Python Modules',
]
@@ -57,7 +58,7 @@ entry_points = {
]}
ftp_url = None
install_requires = ['spark-parser >= 1.8.7, < 1.9.0',
'xdis >= 3.9.0, < 3.10.0']
'xdis >= 4.0.0, < 4.1.0']
license = 'GPL3'
mailing_list = 'python-debugger@googlegroups.com'

View File

@@ -58,7 +58,8 @@
$ git tag release-python-2.4-$VERSION
$ . ./admin-tools/make-dist-newer.sh
$ git tag release-$VERSION
Goto https://github.com/rocky/python-uncompyle6/releases
# Upload single package and look at Rst Formating

View File

@@ -5,4 +5,4 @@ if [[ $0 == ${BASH_SOURCE[0]} ]] ; then
echo "This script should be *sourced* rather than run directly through bash"
exit 1
fi
export PYVERSIONS='3.2.6 3.6.8 3.7.2 2.6.9 3.3.7 2.7.15 3.2.6 3.1.5 3.4.8'
export PYVERSIONS='3.6.8 3.7.2 2.6.9 3.3.7 2.7.15 3.2.6 3.1.5 3.4.8'

View File

@@ -18,15 +18,19 @@ def test_grammar():
right_recursive, dup_rhs) = p.check_sets()
# We have custom rules that create the below
expect_lhs = set(['pos_arg', 'get_iter', 'attribute'])
expect_lhs = set(['pos_arg', 'attribute'])
if PYTHON_VERSION < 3.8:
expect_lhs.add('get_iter')
unused_rhs = set(['list', 'mkfunc',
'mklambda',
'unpack',])
expect_right_recursive = set([('designList',
('store', 'DUP_TOP', 'designList'))])
if PYTHON_VERSION != 3.7:
if PYTHON_VERSION < 3.7:
unused_rhs.add('call')
if PYTHON_VERSION > 2.6:
@@ -61,7 +65,11 @@ def test_grammar():
expect_lhs.add('kwarg')
assert expect_lhs == set(lhs)
# FIXME
if PYTHON_VERSION != 3.8:
assert unused_rhs == set(rhs)
assert expect_right_recursive == right_recursive
expect_dup_rhs = frozenset([('COME_FROM',), ('CONTINUE',), ('JUMP_ABSOLUTE',),

View File

@@ -4,8 +4,8 @@ import sys
"""Setup script for the 'uncompyle6' distribution."""
SYS_VERSION = sys.version_info[0:2]
if not ((2, 6) <= SYS_VERSION <= (3, 7)):
mess = "Python Release 2.6 .. 3.7 are supported in this code branch."
if not ((2, 6) <= SYS_VERSION <= (3, 8)):
mess = "Python Release 2.6 .. 3.8 are supported in this code branch."
if ((2, 4) <= SYS_VERSION <= (2, 7)):
mess += ("\nFor your Python, version %s, use the python-2.4 code/branch." %
sys.version[0:3])

View File

@@ -33,43 +33,48 @@ check-2.6 check-2.7: check-bytecode-2 check-bytecode-3 check-bytecode-1 check-na
#: Run working tests from Python 3.0
check-3.0: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.0 --weak-verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.0-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.0 --weak-verify $(COMPILE)
#: Run working tests from Python 3.1
check-3.1: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.1 --weak-verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.1-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.1 --weak-verify $(COMPILE)
#: Run working tests from Python 3.2
check-3.2: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.2 --weak-verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.2-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.2 --weak-verify $(COMPILE)
#: Run working tests from Python 3.3
check-3.3: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.3 --weak-verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.3-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.3 --weak-verify $(COMPILE)
#: Run working tests from Python 3.4
check-3.4: check-bytecode check-3.4-ok check-2.7-ok
$(PYTHON) test_pythonlib.py --bytecode-3.4 --weak-verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.4-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.4 --weak-verify $(COMPILE)
#: Run working tests from Python 3.5
check-3.5: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.5 --weak-verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.5-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.5 --weak-verify $(COMPILE)
#: Run working tests from Python 3.6
check-3.6: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.6 --weak-verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.6-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.6 --weak-verify $(COMPILE)
#: Run working tests from Python 3.7
check-3.7: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.7 --weak-verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.7-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.7 --weak-verify $(COMPILE)
#: Run working tests from Python 3.8
check-3.8: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.8-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.8 --weak-verify $(COMPILE)
# FIXME
#: this is called when running under pypy3.5-5.8.0 or pypy2-5.6.0
@@ -92,7 +97,8 @@ check-bytecode-2:
check-bytecode-3:
$(PYTHON) test_pythonlib.py --bytecode-3.0 \
--bytecode-3.1 --bytecode-3.2 --bytecode-3.3 \
--bytecode-3.4 --bytecode-3.5 --bytecode-3.6 --bytecode-3.7 \
--bytecode-3.4 --bytecode-3.5 --bytecode-3.6 \
--bytecode-3.7 --bytecode-3.8 \
--bytecode-pypy3.2
#: Check deparsing on selected bytecode 3.x
@@ -217,53 +223,58 @@ grammar-coverage-3.6:
#: Check deparsing Python 2.6
check-bytecode-2.6:
$(PYTHON) test_pythonlib.py --bytecode-2.6 --weak-verify
$(PYTHON) test_pythonlib.py --bytecode-2.6-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-2.6 --weak-verify
#: Check deparsing Python 2.7
check-bytecode-2.7:
$(PYTHON) test_pythonlib.py --bytecode-2.7 --weak-verify
$(PYTHON) test_pythonlib.py --bytecode-2.7-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-2.7 --weak-verify
#: Check deparsing Python 3.0
check-bytecode-3.0:
$(PYTHON) test_pythonlib.py --bytecode-3.0 --weak-verify
$(PYTHON) test_pythonlib.py --bytecode-3.0-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.0 --weak-verify
#: Check deparsing Python 3.1
check-bytecode-3.1:
$(PYTHON) test_pythonlib.py --bytecode-3.1 --weak-verify
$(PYTHON) test_pythonlib.py --bytecode-3.1-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.1 --weak-verify
#: Check deparsing Python 3.2
check-bytecode-3.2:
$(PYTHON) test_pythonlib.py --bytecode-3.2 --weak-verify
$(PYTHON) test_pythonlib.py --bytecode-3.2-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.2 --weak-verify
#: Check deparsing Python 3.3
check-bytecode-3.3:
$(PYTHON) test_pythonlib.py --bytecode-3.3 --weak-verify
$(PYTHON) test_pythonlib.py --bytecode-3.3-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.3 --weak-verify
#: Check deparsing Python 3.4
check-bytecode-3.4:
$(PYTHON) test_pythonlib.py --bytecode-3.4 --weak-verify
$(PYTHON) test_pythonlib.py --bytecode-3.4-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.4 --weak-verify
#: Check deparsing Python 3.5
check-bytecode-3.5:
$(PYTHON) test_pythonlib.py --bytecode-3.5 --weak-verify
$(PYTHON) test_pythonlib.py --bytecode-3.5-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.5 --weak-verify
#: Check deparsing Python 3.6
check-bytecode-3.6:
$(PYTHON) test_pythonlib.py --bytecode-3.6 --weak-verify
$(PYTHON) test_pythonlib.py --bytecode-3.6-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.6 --weak-verify
#: Check deparsing Python 3.7
check-bytecode-3.7:
$(PYTHON) test_pythonlib.py --bytecode-3.7 --weak-verify
#: Check deparsing Python 3.8
check-bytecode-3.8:
$(PYTHON) test_pythonlib.py --bytecode-3.8-run --verify-run
$(PYTHON) test_pythonlib.py --bytecode-3.8 --weak-verify
#: short tests for bytecodes only for this version of Python
check-native-short:
$(PYTHON) test_pythonlib.py --bytecode-$(PYTHON_VERSION) --weak-verify $(COMPILE)

View File

@@ -4,12 +4,19 @@
import os, sys, py_compile
assert len(sys.argv) >= 2
version = sys.version[0:3]
for path in sys.argv[1:]:
if sys.argv[1] == '--run':
suffix = '_run'
py_source = sys.argv[2:]
else:
suffix = ''
py_source = sys.argv[1:]
for path in py_source:
short = os.path.basename(path)
if hasattr(sys, 'pypy_version_info'):
cfile = "bytecode_pypy%s/%s" % (version, short) + 'c'
cfile = "bytecode_pypy%s%s/%s" % (version, suffix, short) + 'c'
else:
cfile = "bytecode_%s/%s" % (version, short) + 'c'
cfile = "bytecode_%s%s/%s" % (version, suffix, short) + 'c'
print("byte-compiling %s to %s" % (path, cfile))
py_compile.compile(path, cfile)
if isinstance(version, str) or version >= (2, 6, 0):

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,12 +1,18 @@
# Python 2.7 sqlalchemy-1.013/sql/crud.py
def _extend_values_for_multiparams(compiler, stmt, c):
c(
[
(
(compiler() if compiler()
else compiler())
if c in stmt else compiler(),
)
]
for i in enumerate(stmt)
# Adapted from Python 2.7 sqlalchemy-1.013/sql/crud.py
# Bug was in handling generator comprehension
# In 2.6, 2.7 JUMP_ABSOLUTEs rather than JUMP_BACKs are generated
# RUNNABLE!
def extend(stmt, a, c, c1, c2, c3):
return c(
([ (5 if c1 else c2)
if a else c3
] for i in enumerate(stmt))
)
def foo(gen):
return list(gen)
assert extend([0], 0, foo, True, 'c2', 'c3') == [['c3']]
assert extend([0, 1], 1, foo, False, 'c2', 'c3') == [['c2'], ['c2']]
assert extend([0, 1], False, foo, False, 'c2', 'c3') == [['c3'], ['c3']]

View File

@@ -1,8 +1,11 @@
# In Python 3.3+ this uses grammar rule
# compare_chained2 ::= expr COMPARE_OP RETURN_VALUE
# In Python 3.6 uses this uses grammar rule
# compare_chained2 ::= expr COMPARE_OP come_froms JUMP_FORWARD
# Seen in Python 3.3 ipaddress.py
# RUNNABLE!
def _is_valid_netmask(netmask):
return 0 <= netmask <= 10
@@ -10,4 +13,4 @@ def _is_valid_netmask(netmask):
# detections
# See in 2.6.9 quopri.py ishex():
'0' <= __file__ <= '9' or 'a' <= __file__ <= 'f' or 'A' <= __file__ <= 'F'
assert not '0' <= __file__ <= '9' or 'a' <= __file__ <= 'f' or 'A' <= __file__ <= 'F'

View File

@@ -1,6 +1,12 @@
# From Python 3.3.6 hmac.py
# Problem was getting wrong placement of positional args
# Problem was getting wrong placement of positional args.
# In 3.6+ paramter handling changes
# RUNNABLE!
digest_cons = lambda d=b'': 5
# Handle single kwarg
lambda *, d=0: None
x = lambda *, d=0: d
assert x(d=1) == 1
assert x() == 0

View File

@@ -1,28 +1,32 @@
# From Python 3.4 asynchat.py
# Tests presence or absense of
# SETUP_LOOP testexpr return_stmts POP_BLOCK COME_FROM_LOOP
# Note: that there is no JUMP_BACK because of the return_stmts.
def initiate_send(self, num_sent, first):
while self.producer_fifo and self.connected:
def initiate_send(a, b, c, num_sent):
while a and b:
try:
5
except OSError:
return
1 / (b - 1)
except ZeroDivisionError:
return 1
if num_sent:
if first:
self.producer_fifo = '6'
else:
del self.producer_fifo[0]
return
c = 2
return c
# FIXME: this causes a parse error:
# def initiate_send(self):
# while self.producer_fifo and self.connected:
# try:
# 6
# except OSError:
# return
def initiate_send2(a, b):
while a and b:
try:
1 / (b - 1)
except ZeroDivisionError:
return 1
# return
return 2
assert initiate_send(1, 1, 2, False) == 1
assert initiate_send(1, 2, 3, False) == 3
assert initiate_send(1, 2, 3, True) == 2
assert initiate_send2(1, 1) == 1
assert initiate_send2(1, 2) == 2

View File

@@ -1,3 +1,4 @@
# Self-checking test.
# Python 3 bug in not detecting the end bounds of if elif.
def testit(b):
if b == 1:

View File

@@ -1,4 +1,5 @@
# Self-checking 3.6+ string interpolation tests
# Self-checking test.
# String interpolation tests
var1 = 'x'
var2 = 'y'

View File

@@ -0,0 +1,19 @@
# From 3.6 _collections.abc.py
# Bug was try/execpt parsing detection since 3.6 removes
# a JUMP_FORWARD from earlier 3.xs.
# This could also get confused with try/else.
# RUNNABLE!
def iter(self):
i = 0
try:
while True:
v = self[i]
yield v
i += 1
except IndexError:
return
A = [10, 20, 30]
assert list(iter(A)) == A

View File

@@ -1,8 +1,17 @@
# Self-checking test.
# Tests:
# forstmt ::= SETUP_LOOP expr _for store
# for ::= SETUP_LOOP expr for_iter store
# for_block POP_BLOCK COME_FROM
# In 3.8+
# for ::= expr for_iter store
# for_block POP_BLOCK COME_FROM
for a in [1]:
c = 2
for a in range(2):
c = 2
c = 0
for a in [1]:
c += a
assert c == 1, c
for a in range(3):
c += a
assert c == 4, c

View File

@@ -1,12 +1,26 @@
# Self-checking test.
# Mixed boolean expresions
b = True
assert b, 'b = True'
c = False
assert not c, 'c = False'
d = True
a = b and c or d
assert a, 'b and c or d'
a = (b or c) and d
assert a, '(b or c) and d'
a = b or c or d
assert a, 'b or c or d'
a = b and c and d
assert not a, 'b and c and d'
a = b or c and d
assert a
a = b and (c or d)
assert a
a = b and c or d
assert a
a = (b or c and d) and b
assert a
a = (b or c and d or a) and b
assert a

View File

@@ -1,8 +1,15 @@
# Self-checking test.
# Tests of Python slice operators
ary = [1,2,3,4,5]
# Forces BUILD_SLICE 2 on 3.x
ary[:2]
ary[2:]
a = ary[:2]
assert a == [1, 2]
a = ary[2:]
assert a == [3, 4, 5]
# Forces BUILD_SLICE 3
ary[1:4:2]
a = ary[1:4:2]
assert a == [2, 4]

View File

@@ -81,7 +81,7 @@ for vers in (2.7, 3.4, 3.5, 3.6):
for vers in (1.3, 1.4, 1.5,
2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7,
3.0, 3.1, 3.2, 3.3,
3.4, 3.5, 3.6, 3.7, 'pypy3.2', 'pypy2.7'):
3.4, 3.5, 3.6, 3.7, 3.8, 'pypy3.2', 'pypy2.7'):
bytecode = "bytecode_%s" % vers
key = "bytecode-%s" % vers
test_options[key] = (bytecode, PYC, bytecode, vers)

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# Mode: -*- python -*-
#
# Copyright (c) 2015-2017 by Rocky Bernstein
# Copyright (c) 2015-2017, 2019 by Rocky Bernstein
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
#
from __future__ import print_function
@@ -30,7 +30,8 @@ Options:
-> /tmp/bla/fasel.pyc_dis, /tmp/bar/foo.pyc_dis
uncompyle6 -o /tmp /usr/lib/python1.5
-> /tmp/smtplib.pyc_dis ... /tmp/lib-tk/FixTk.pyc_dis
-c <file> attempts a disassembly after compiling <file>
--compile | -c <python-file>
attempts a decompilation after compiling <python-file>
-d print timestamps
-p <integer> use <integer> number of processes
-r recurse directories looking for .pyc and .pyo files
@@ -43,9 +44,9 @@ Options:
--help show this message
Debugging Options:
--asm -a include byte-code (disables --verify)
--grammar -g show matching grammar
--tree -t include syntax tree (disables --verify)
--asm | -a include byte-code (disables --verify)
--grammar | -g show matching grammar
--tree | -t include syntax tree (disables --verify)
--tree++ add template rules to --tree when possible
Extensions of generated files:
@@ -61,10 +62,7 @@ from uncompyle6.main import main, status_msg
from uncompyle6.version import VERSION
def usage():
print("""usage:
%s [--verify | --weak-verify ] [--asm] [--tree[+]] [--grammar] [-o <path>] FILE|DIR...
%s [--help | -h | --version | -V]
""" % (program, program))
print(__doc__)
sys.exit(1)
@@ -72,9 +70,9 @@ def main_bin():
if not (sys.version_info[0:2] in ((2, 6), (2, 7), (3, 0),
(3, 1), (3, 2), (3, 3),
(3, 4), (3, 5), (3, 6),
(3, 7)
(3, 7), (3, 8)
)):
print('Error: %s requires Python 2.6-3.7' % program,
print('Error: %s requires Python 2.6-3.8' % program,
file=sys.stderr)
sys.exit(-1)
@@ -82,13 +80,13 @@ def main_bin():
numproc = 0
outfile = '-'
out_base = None
codes = []
source_paths = []
timestamp = False
timestampfmt = "# %Y.%m.%d %H:%M:%S %Z"
try:
opts, files = getopt.getopt(sys.argv[1:], 'hagtdrVo:c:p:',
'help asm grammar linemaps recurse '
opts, pyc_paths = getopt.getopt(sys.argv[1:], 'hac:gtdrVo:p:',
'help asm compile= grammar linemaps recurse '
'timestamp tree tree+ '
'fragments verify verify-run version '
'weak-verify '
@@ -130,8 +128,8 @@ def main_bin():
outfile = val
elif opt in ('--timestamp', '-d'):
timestamp = True
elif opt == '-c':
codes.append(val)
elif opt in ('--compile', '-c'):
source_paths.append(val)
elif opt == '-p':
numproc = int(val)
elif opt in ('--recurse', '-r'):
@@ -143,33 +141,33 @@ def main_bin():
# expand directory if specified
if recurse_dirs:
expanded_files = []
for f in files:
for f in pyc_paths:
if os.path.isdir(f):
for root, _, dir_files in os.walk(f):
for df in dir_files:
if df.endswith('.pyc') or df.endswith('.pyo'):
expanded_files.append(os.path.join(root, df))
files = expanded_files
pyc_paths = expanded_files
# argl, commonprefix works on strings, not on path parts,
# thus we must handle the case with files in 'some/classes'
# and 'some/cmds'
src_base = os.path.commonprefix(files)
src_base = os.path.commonprefix(pyc_paths)
if src_base[-1:] != os.sep:
src_base = os.path.dirname(src_base)
if src_base:
sb_len = len( os.path.join(src_base, '') )
files = [f[sb_len:] for f in files]
pyc_paths = [f[sb_len:] for f in pyc_paths]
if not files:
print("No files given", file=sys.stderr)
if not pyc_paths and not source_paths:
print("No input files given to decompile", file=sys.stderr)
usage()
if outfile == '-':
outfile = None # use stdout
elif outfile and os.path.isdir(outfile):
out_base = outfile; outfile = None
elif outfile and len(files) > 1:
elif outfile and len(pyc_paths) > 1:
out_base = outfile; outfile = None
if timestamp:
@@ -177,10 +175,10 @@ def main_bin():
if numproc <= 1:
try:
result = main(src_base, out_base, files, None, outfile,
result = main(src_base, out_base, pyc_paths, source_paths, outfile,
**options)
result = list(result) + [options.get('do_verify', None)]
if len(files) > 1:
if len(pyc_paths) > 1:
mess = status_msg(do_verify, *result)
print('# ' + mess)
pass
@@ -196,8 +194,8 @@ def main_bin():
except ImportError:
from queue import Empty
fqueue = Queue(len(files)+numproc)
for f in files:
fqueue = Queue(len(pyc_paths)+numproc)
for f in pyc_paths:
fqueue.put(f)
for i in range(numproc):
fqueue.put(None)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018 Rocky Bernstein <rocky@gnu.org>
# Copyright (C) 2018-2019 Rocky Bernstein <rocky@gnu.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
import datetime, os, subprocess, sys, tempfile
import datetime, py_compile, os, subprocess, sys, tempfile
from uncompyle6 import verify, IS_PYPY, PYTHON_VERSION
from xdis.code import iscode
@@ -119,6 +119,22 @@ def decompile(
# deparsing failed
raise pysource.SourceWalkerError(str(e))
def compile_file(source_path):
if source_path.endswith('.py'):
basename = source_path[:-3]
else:
basename = source_path
if hasattr(sys, 'pypy_version_info'):
bytecode_path = "%s-pypy%s.pyc" % (basename, PYTHON_VERSION)
else:
bytecode_path = "%s-%s.pyc" % (basename, PYTHON_VERSION)
print("compiling %s to %s" % (source_path, bytecode_path))
py_compile.compile(source_path, bytecode_path, 'exec')
return bytecode_path
def decompile_file(filename, outstream=None, showasm=None, showast=False,
showgrammar=False, mapstream=None, do_fragments=False):
"""
@@ -150,7 +166,7 @@ def decompile_file(filename, outstream=None, showasm=None, showast=False,
# FIXME: combine into an options parameter
def main(in_base, out_base, files, codes, outfile=None,
def main(in_base, out_base, compiled_files, source_files, outfile=None,
showasm=None, showast=False, do_verify=False,
showgrammar=False, raise_on_error=False,
do_linemaps=False, do_fragments=False):
@@ -160,8 +176,6 @@ def main(in_base, out_base, files, codes, outfile=None,
files list of filenames to be uncompyled (relative to in_base)
outfile write output to this filename (overwrites out_base)
Note: `codes` is not use. Historical compatability?
For redirecting output to
- <filename> outfile=<filename> (out_base is ignored)
- files below out_base out_base=...
@@ -171,7 +185,10 @@ def main(in_base, out_base, files, codes, outfile=None,
current_outfile = outfile
linemap_stream = None
for filename in files:
for source_path in source_files:
compiled_files.append(compile_file(source_path))
for filename in compiled_files:
infile = os.path.join(in_base, filename)
# print("XXX", infile)
if not os.path.exists(infile):

View File

@@ -747,6 +747,12 @@ def get_python_parser(
p = parse37.Python37Parser(debug_parser)
else:
p = parse37.Python37ParserSingle(debug_parser)
elif version == 3.8:
import uncompyle6.parsers.parse38 as parse38
if compile_mode == 'exec':
p = parse38.Python38Parser(debug_parser)
else:
p = parse38.Python38ParserSingle(debug_parser)
else:
if compile_mode == 'exec':
p = parse3.Python3Parser(debug_parser)

View File

@@ -267,6 +267,8 @@ class Python26Parser(Python2Parser):
# Note: preserve positions 0 2 and 4 for semantic actions
conditional_not ::= expr jmp_true expr jf_cf_pop expr COME_FROM
conditional ::= expr jmp_false expr jf_cf_pop expr come_from_opt
conditional ::= expr jmp_false expr ja_cf_pop expr
expr ::= conditional_not
and ::= expr JUMP_IF_FALSE POP_TOP expr JUMP_IF_FALSE POP_TOP

View File

@@ -41,7 +41,6 @@ class Python27Parser(Python2Parser):
comp_body ::= set_comp_body
comp_for ::= expr for_iter store comp_iter JUMP_BACK
comp_iter ::= comp_if
comp_iter ::= comp_body
dict_comp_body ::= expr expr MAP_ADD
@@ -123,6 +122,7 @@ class Python27Parser(Python2Parser):
conditional_true ::= expr JUMP_FORWARD expr COME_FROM
conditional ::= expr jmp_false expr JUMP_FORWARD expr COME_FROM
conditional ::= expr jmp_false expr JUMP_ABSOLUTE expr
"""
def p_stmt27(self, args):

View File

@@ -98,8 +98,9 @@ class Python3Parser(PythonParser):
sstmt ::= return RETURN_LAST
return_if_stmts ::= return_if_stmt come_from_opt
return_if_stmts ::= _stmts return_if_stmt
return_if_stmts ::= _stmts return_if_stmt _come_froms
return_if_stmt ::= ret_expr RETURN_END_IF
returns ::= _stmts return_if_stmt
stmt ::= break
break ::= BREAK_LOOP
@@ -952,6 +953,12 @@ class Python3Parser(PythonParser):
opname))
self.add_unique_rule(rule, opname, token.attr, customize)
else:
rule = ('mklambda ::= %sLOAD_LAMBDA LOAD_CONST %s' %
(('expr ' * stack_count), opname))
self.add_unique_rule(rule, opname, token.attr, customize)
rule = ('mkfunc ::= %s%s%s%s' %
('expr ' * stack_count,
'load_closure ' * closure,
@@ -1148,6 +1155,8 @@ class Python3Parser(PythonParser):
self.check_reduce['ifelsestmt'] = 'AST'
self.check_reduce['annotate_tuple'] = 'noAST'
self.check_reduce['kwarg'] = 'noAST'
if self.version < 3.6:
# 3.6+ can remove a JUMP_FORWARD which messes up our testing here
self.check_reduce['try_except'] = 'AST'
# FIXME: remove parser errors caused by the below
@@ -1190,7 +1199,9 @@ class Python3Parser(PythonParser):
last += 1
if last == n:
return False
return tokens[first].attr > tokens[last].offset
# 3.8+ Doesn't have SETUP_LOOP
return self.version < 3.8 and tokens[first].attr > tokens[last].offset
elif rule == ('try_except',
('SETUP_EXCEPT', 'suite_stmts_opt', 'POP_BLOCK',
'except_handler', 'opt_come_from_except')):

View File

@@ -17,15 +17,10 @@ spark grammar differences over Python 3.3 for Python 3.4
"""
from uncompyle6.parser import PythonParserSingle
from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
from uncompyle6.parsers.parse33 import Python33Parser
class Python34Parser(Python33Parser):
def __init__(self, debug_parser=PARSER_DEFAULT_DEBUG):
super(Python34Parser, self).__init__(debug_parser)
self.customized = {}
def p_misc34(self, args):
"""
expr ::= LOAD_ASSERT
@@ -34,6 +29,8 @@ class Python34Parser(Python33Parser):
# passtmt is needed for semantic actions to add "pass"
suite_stmts_opt ::= pass
whilestmt ::= SETUP_LOOP testexpr returns come_froms POP_BLOCK COME_FROM_LOOP
# Seems to be needed starting 3.4.4 or so
while1stmt ::= SETUP_LOOP l_stmts
COME_FROM JUMP_BACK POP_BLOCK COME_FROM_LOOP

View File

@@ -28,7 +28,6 @@ class Python35Parser(Python34Parser):
while1stmt ::= SETUP_LOOP l_stmts POP_BLOCK COME_FROM_LOOP
while1elsestmt ::= SETUP_LOOP l_stmts JUMP_BACK
POP_BLOCK else_suite COME_FROM_LOOP
whilestmt ::= SETUP_LOOP testexpr returns POP_BLOCK COME_FROM_LOOP
# The following rule is for Python 3.5+ where we can have stuff like
# while ..
@@ -106,7 +105,6 @@ class Python35Parser(Python34Parser):
return_if_stmt ::= ret_expr RETURN_END_IF POP_BLOCK
return_if_lambda ::= RETURN_END_IF_LAMBDA COME_FROM
jb_else ::= JUMP_BACK ELSE
ifelsestmtc ::= testexpr c_stmts_opt JUMP_FORWARD else_suitec
ifelsestmtl ::= testexpr c_stmts_opt jb_else else_suitel
@@ -160,7 +158,7 @@ class Python35Parser(Python34Parser):
call_token = tokens[i+1]
rule = 'call ::= expr unmapexpr ' + call_token.kind
self.addRule(rule, nop_func)
elif opname == 'BEFORE_ASYNC_WITH':
elif opname == 'BEFORE_ASYNC_WITH' and self.version < 3.8:
# Some Python 3.5+ async additions
rules_str = """
async_with_stmt ::= expr

View File

@@ -68,6 +68,22 @@ class Python36Parser(Python35Parser):
for_block POP_BLOCK
COME_FROM_LOOP
stmt ::= async_for_stmt36
async_for_stmt36 ::= SETUP_LOOP expr
GET_AITER
LOAD_CONST YIELD_FROM
SETUP_EXCEPT GET_ANEXT LOAD_CONST
YIELD_FROM
store
POP_BLOCK JUMP_BACK COME_FROM_EXCEPT DUP_TOP
LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY for_block
COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT
POP_TOP POP_BLOCK
COME_FROM_LOOP
async_forelse_stmt ::= SETUP_LOOP expr
GET_AITER
LOAD_CONST YIELD_FROM SETUP_EXCEPT GET_ANEXT LOAD_CONST
@@ -117,6 +133,8 @@ class Python36Parser(Python35Parser):
stmt ::= tryfinally_return_stmt
tryfinally_return_stmt ::= SETUP_FINALLY suite_stmts_opt POP_BLOCK LOAD_CONST
COME_FROM_FINALLY
compare_chained2 ::= expr COMPARE_OP come_froms JUMP_FORWARD
"""
def customize_grammar_rules(self, tokens, customize):
@@ -231,8 +249,6 @@ class Python36Parser(Python35Parser):
self.addRule(rule, nop_func)
elif opname == 'SETUP_WITH':
rules_str = """
withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt POP_BLOCK LOAD_CONST
WITH_CLEANUP_START WITH_CLEANUP_FINISH END_FINALLY
withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt COME_FROM_WITH
WITH_CLEANUP_START WITH_CLEANUP_FINISH END_FINALLY
@@ -240,6 +256,19 @@ class Python36Parser(Python35Parser):
withasstmt ::= expr SETUP_WITH store suite_stmts_opt COME_FROM_WITH
WITH_CLEANUP_START WITH_CLEANUP_FINISH END_FINALLY
"""
if self.version < 3.8:
rules_str += """
withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt POP_BLOCK
LOAD_CONST
WITH_CLEANUP_START WITH_CLEANUP_FINISH END_FINALLY
"""
else:
rules_str += """
withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt POP_BLOCK
BEGIN_FINALLY COME_FROM_WITH
WITH_CLEANUP_START WITH_CLEANUP_FINISH
END_FINALLY
"""
self.addRule(rules_str, nop_func)
pass
pass
@@ -352,6 +381,7 @@ class Python36Parser(Python35Parser):
pass
pass
return False
class Python36ParserSingle(Python36Parser, PythonParserSingle):
pass

View File

@@ -31,6 +31,7 @@ class Python37Parser(Python36Parser):
"""
# Where does the POP_TOP really belong?
stmt ::= import37
stmt ::= async_for_stmt37
import37 ::= import POP_TOP
async_for_stmt ::= SETUP_LOOP expr
@@ -46,6 +47,19 @@ class Python37Parser(Python36Parser):
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP POP_BLOCK
COME_FROM_LOOP
# Order of LOAD_CONST YIELD_FROM is switched from 3.6 to save a LOAD_CONST
async_for_stmt37 ::= SETUP_LOOP expr
GET_AITER
SETUP_EXCEPT GET_ANEXT
LOAD_CONST YIELD_FROM
store
POP_BLOCK JUMP_BACK COME_FROM_EXCEPT DUP_TOP
LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY for_block COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT
POP_TOP POP_BLOCK
COME_FROM_LOOP
async_forelse_stmt ::= SETUP_LOOP expr
GET_AITER
SETUP_EXCEPT GET_ANEXT LOAD_CONST
@@ -69,15 +83,31 @@ class Python37Parser(Python36Parser):
call ::= expr CALL_METHOD_0
testtrue ::= compare_chained37
testfalse ::= compare_chained37_false
compare_chained37 ::= expr compare_chained1a_37
compare_chained37 ::= expr compare_chained1b_37
compare_chained2a_37 ::= expr COMPARE_OP POP_JUMP_IF_TRUE JUMP_FORWARD
compare_chained2b_37 ::= expr COMPARE_OP COME_FROM POP_JUMP_IF_FALSE JUMP_FORWARD ELSE
compare_chained37_false ::= expr compare_chained1_false_37
compare_chained1a_37 ::= expr DUP_TOP ROT_THREE COMPARE_OP POP_JUMP_IF_FALSE
compare_chained1a_37 ::= expr DUP_TOP ROT_THREE COMPARE_OP POP_JUMP_IF_FALSE
compare_chained2a_37 ELSE POP_TOP COME_FROM
compare_chained1b_37 ::= expr DUP_TOP ROT_THREE COMPARE_OP POP_JUMP_IF_FALSE
compare_chained2b_37 POP_TOP JUMP_FORWARD COME_FROM
compare_chained1_false_37 ::= expr DUP_TOP ROT_THREE COMPARE_OP POP_JUMP_IF_FALSE
compare_chained2c_37 POP_TOP JUMP_FORWARD COME_FROM
compare_chained2a_37 ::= expr COMPARE_OP POP_JUMP_IF_TRUE JUMP_FORWARD
compare_chained2a_false_37 ::= expr COMPARE_OP POP_JUMP_IF_FALSE JUMP_FORWARD
compare_chained2b_37 ::= expr COMPARE_OP come_from_opt POP_JUMP_IF_FALSE JUMP_FORWARD ELSE
compare_chained2c_37 ::= expr DUP_TOP ROT_THREE COMPARE_OP come_from_opt POP_JUMP_IF_FALSE
compare_chained2a_false_37 ELSE
_ifstmts_jump ::= c_stmts_opt come_froms
"""
def customize_grammar_rules(self, tokens, customize):
@@ -93,6 +123,18 @@ class Python37Parser(Python36Parser):
JUMP_ABSOLUTE END_FINALLY COME_FROM
for_block POP_BLOCK
else_suite COME_FROM_LOOP
stmt ::= async_for_stmt36
async_for_stmt36 ::= SETUP_LOOP expr
GET_AITER
LOAD_CONST YIELD_FROM SETUP_EXCEPT GET_ANEXT LOAD_CONST
YIELD_FROM
store
POP_BLOCK JUMP_BACK COME_FROM_EXCEPT DUP_TOP
LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY continues COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT
POP_TOP POP_BLOCK
COME_FROM_LOOP
""")
super(Python37Parser, self).customize_grammar_rules(tokens, customize)

View File

@@ -0,0 +1,283 @@
# Copyright (c) 2017-2019 Rocky Bernstein
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
spark grammar differences over Python 3.7 for Python 3.8
"""
from __future__ import print_function
from uncompyle6.parser import PythonParserSingle
from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
from uncompyle6.parsers.parse37 import Python37Parser
class Python38Parser(Python37Parser):
def p_38misc(self, args):
"""
stmt ::= async_for_stmt38
stmt ::= async_forelse_stmt38
stmt ::= for38
stmt ::= forelsestmt38
stmt ::= forelselaststmt38
stmt ::= forelselaststmtl38
stmt ::= tryfinally38
stmt ::= try_elsestmtl38
stmt ::= try_except_ret38
stmt ::= try_except38
stmt ::= whilestmt38
stmt ::= whileTruestmt38
stmt ::= call
# FIXME: this should be restricted to being inside a try block
stmt ::= except_ret38
# FIXME: this should be added only when seeing GET_AITER or YIELD_FROM
async_for_stmt38 ::= expr
GET_AITER
SETUP_FINALLY
GET_ANEXT
LOAD_CONST
YIELD_FROM
POP_BLOCK
store for_block
COME_FROM_FINALLY
END_ASYNC_FOR
# FIXME: come froms after the else_suite or END_ASYNC_FOR distinguish which of
# for / forelse is used. Add come froms and check of add up control-flow detection phase.
async_forelse_stmt38 ::= expr
GET_AITER
SETUP_FINALLY
GET_ANEXT
LOAD_CONST
YIELD_FROM
POP_BLOCK
store for_block
COME_FROM_FINALLY
END_ASYNC_FOR
else_suite
async_with_stmt ::= expr BEFORE_ASYNC_WITH GET_AWAITABLE LOAD_CONST YIELD_FROM
SETUP_ASYNC_WITH POP_TOP
suite_stmts
POP_TOP POP_BLOCK
BEGIN_FINALLY
WITH_CLEANUP_START
GET_AWAITABLE LOAD_CONST YIELD_FROM
WITH_CLEANUP_FINISH END_FINALLY
async_with_as_stmt ::= expr BEFORE_ASYNC_WITH GET_AWAITABLE LOAD_CONST YIELD_FROM
SETUP_ASYNC_WITH store
suite_stmts
POP_TOP POP_BLOCK
BEGIN_FINALLY
WITH_CLEANUP_START
GET_AWAITABLE LOAD_CONST YIELD_FROM
WITH_CLEANUP_FINISH END_FINALLY
return ::= ret_expr ROT_TWO POP_TOP RETURN_VALUE
for38 ::= expr get_iter store for_block JUMP_BACK
for38 ::= expr for_iter store for_block JUMP_BACK
for38 ::= expr for_iter store for_block JUMP_BACK POP_BLOCK
forelsestmt38 ::= expr for_iter store for_block POP_BLOCK else_suite
forelselaststmt38 ::= expr for_iter store for_block POP_BLOCK else_suitec
forelselaststmtl38 ::= expr for_iter store for_block POP_BLOCK else_suitel
whilestmt38 ::= testexpr l_stmts_opt COME_FROM JUMP_BACK POP_BLOCK
whilestmt38 ::= testexpr l_stmts_opt JUMP_BACK POP_BLOCK
whilestmt38 ::= testexpr returns POP_BLOCK
whilestmt38 ::= testexpr l_stmts JUMP_BACK
# while1elsestmt ::= l_stmts JUMP_BACK
whileTruestmt ::= l_stmts JUMP_BACK POP_BLOCK
while1stmt ::= l_stmts COME_FROM_LOOP
while1stmt ::= l_stmts COME_FROM JUMP_BACK COME_FROM_LOOP
whileTruestmt38 ::= l_stmts JUMP_BACK
for_block ::= l_stmts_opt _come_from_loops JUMP_BACK
except_cond1 ::= DUP_TOP expr COMPARE_OP jmp_false
POP_TOP POP_TOP POP_TOP
POP_EXCEPT
try_elsestmtl38 ::= SETUP_FINALLY suite_stmts_opt POP_BLOCK
except_handler38 COME_FROM
else_suitel opt_come_from_except
try_except ::= SETUP_FINALLY suite_stmts_opt POP_BLOCK
except_handler38
try_except38 ::= SETUP_FINALLY POP_BLOCK POP_TOP suite_stmts_opt
except_handler38a
try_except_ret38 ::= SETUP_FINALLY expr POP_BLOCK
RETURN_VALUE except_ret38a
except_ret38 ::= SETUP_FINALLY expr ROT_FOUR POP_BLOCK POP_EXCEPT
CALL_FINALLY RETURN_VALUE COME_FROM_FINALLY
suite_stmts_opt END_FINALLY
except_ret38a ::= COME_FROM_FINALLY POP_TOP POP_TOP POP_TOP
expr ROT_FOUR
POP_EXCEPT RETURN_VALUE END_FINALLY
except_handler38 ::= JUMP_FORWARD COME_FROM_FINALLY
except_stmts END_FINALLY opt_come_from_except
except_handler38a ::= COME_FROM_FINALLY POP_TOP POP_TOP POP_TOP
POP_EXCEPT POP_TOP stmts END_FINALLY
tryfinallystmt ::= SETUP_FINALLY suite_stmts_opt POP_BLOCK
BEGIN_FINALLY COME_FROM_FINALLY suite_stmts_opt
END_FINALLY
tryfinally38 ::= SETUP_FINALLY POP_BLOCK CALL_FINALLY
returns
COME_FROM_FINALLY END_FINALLY suite_stmts
tryfinally38 ::= SETUP_FINALLY POP_BLOCK CALL_FINALLY
returns
COME_FROM_FINALLY POP_FINALLY returns
END_FINALLY
tryfinally_return_stmt ::= SETUP_FINALLY suite_stmts_opt POP_BLOCK
BEGIN_FINALLY COME_FROM_FINALLY
POP_FINALLY suite_stmts_opt END_FINALLY
"""
def __init__(self, debug_parser=PARSER_DEFAULT_DEBUG):
super(Python38Parser, self).__init__(debug_parser)
self.customized = {}
def customize_grammar_rules(self, tokens, customize):
self.remove_rules("""
stmt ::= async_for_stmt37
stmt ::= for
stmt ::= forelsestmt
stmt ::= try_except36
async_for_stmt ::= SETUP_LOOP expr
GET_AITER
SETUP_EXCEPT GET_ANEXT LOAD_CONST
YIELD_FROM
store
POP_BLOCK JUMP_FORWARD COME_FROM_EXCEPT DUP_TOP
LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY COME_FROM
for_block
COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP POP_BLOCK
COME_FROM_LOOP
async_for_stmt37 ::= SETUP_LOOP expr
GET_AITER
SETUP_EXCEPT GET_ANEXT
LOAD_CONST YIELD_FROM
store
POP_BLOCK JUMP_BACK COME_FROM_EXCEPT DUP_TOP
LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY for_block COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT
POP_TOP POP_BLOCK
COME_FROM_LOOP
async_forelse_stmt ::= SETUP_LOOP expr
GET_AITER
SETUP_EXCEPT GET_ANEXT LOAD_CONST
YIELD_FROM
store
POP_BLOCK JUMP_FORWARD COME_FROM_EXCEPT DUP_TOP
LOAD_GLOBAL COMPARE_OP POP_JUMP_IF_TRUE
END_FINALLY COME_FROM
for_block
COME_FROM
POP_TOP POP_TOP POP_TOP POP_EXCEPT POP_TOP POP_BLOCK
else_suite COME_FROM_LOOP
for ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK
for ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK NOP
for_block ::= l_stmts_opt COME_FROM_LOOP JUMP_BACK
forelsestmt ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK else_suite
forelselaststmt ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK else_suitec
forelselaststmtl ::= SETUP_LOOP expr for_iter store for_block POP_BLOCK else_suitel
tryelsestmtl3 ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
except_handler COME_FROM else_suitel
opt_come_from_except
try_except ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
except_handler opt_come_from_except
tryfinallystmt ::= SETUP_FINALLY suite_stmts_opt POP_BLOCK
LOAD_CONST COME_FROM_FINALLY suite_stmts_opt
END_FINALLY
tryfinally36 ::= SETUP_FINALLY returns
COME_FROM_FINALLY suite_stmts_opt END_FINALLY
tryfinally_return_stmt ::= SETUP_FINALLY suite_stmts_opt POP_BLOCK
LOAD_CONST COME_FROM_FINALLY
""")
super(Python37Parser, self).customize_grammar_rules(tokens, customize)
self.check_reduce['ifstmt'] = 'tokens'
self.check_reduce['whileTruestmt38'] = 'tokens'
def reduce_is_invalid(self, rule, ast, tokens, first, last):
invalid = super(Python38Parser,
self).reduce_is_invalid(rule, ast,
tokens, first, last)
if invalid:
return invalid
if rule[0] == 'ifstmt':
# Make sure jumps don't extend beyond the end of the if statement.
l = last
if l == len(tokens):
l -= 1
if isinstance(tokens[l].offset, str):
last_offset = int(tokens[l].offset.split('_')[0], 10)
else:
last_offset = tokens[l].offset
for i in range(first, l):
t = tokens[i]
if t.kind == 'POP_JUMP_IF_FALSE':
if t.attr > last_offset:
return True
pass
pass
pass
elif rule[0] == 'whileTruestmt38':
t = tokens[last-1]
if t.kind == 'JUMP_BACK':
return t.attr != tokens[first].offset
pass
return False
class Python38ParserSingle(Python38Parser, PythonParserSingle):
pass
if __name__ == '__main__':
# Check grammar
p = Python38Parser()
p.check_grammar()
from uncompyle6 import PYTHON_VERSION, IS_PYPY
if PYTHON_VERSION == 3.8:
lhs, rhs, tokens, right_recursive = p.check_sets()
from uncompyle6.scanner import get_scanner
s = get_scanner(PYTHON_VERSION, IS_PYPY)
opcode_set = set(s.opc.opname).union(set(
"""JUMP_BACK CONTINUE RETURN_END_IF COME_FROM
LOAD_GENEXPR LOAD_ASSERT LOAD_SETCOMP LOAD_DICTCOMP LOAD_CLASSNAME
LAMBDA_MARKER RETURN_LAST
""".split()))
remain_tokens = set(tokens) - opcode_set
import re
remain_tokens = set([re.sub(r'_\d+$', '', t) for t in remain_tokens])
remain_tokens = set([re.sub('_CONT$', '', t) for t in remain_tokens])
remain_tokens = set(remain_tokens) - opcode_set
print(remain_tokens)
# print(sorted(p.rule2name.items()))

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2016, 2018 by Rocky Bernstein
# Copyright (c) 2016, 2018-2019 by Rocky Bernstein
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# Copyright (c) 1999 John Aycock
@@ -39,7 +39,7 @@ from xdis.util import code2num
# Note: these all have to be floats
PYTHON_VERSIONS = frozenset((1.3, 1.4, 1.5,
2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7,
3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7))
3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8))
CANONIC2VERSION = dict((canonic_python_version[str(v)], v) for v in PYTHON_VERSIONS)

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2018 by Rocky Bernstein
# Copyright (c) 2018-2019 by Rocky Bernstein
"""
Python 1.3 bytecode decompiler massaging.
@@ -25,7 +25,7 @@ class Scanner13(scan.Scanner14):
self.version = 1.3
return
# def ingest22(self, co, classname=None, code_objects={}, show_asm=None):
# def ingest(self, co, classname=None, code_objects={}, show_asm=None):
# tokens, customize = self.parent_ingest(co, classname, code_objects, show_asm)
# tokens = [t for t in tokens if t.kind != 'SET_LINENO']

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2018 by Rocky Bernstein
# Copyright (c) 2018-2019 by Rocky Bernstein
"""
Python 1.4 bytecode decompiler massaging.
@@ -26,7 +26,7 @@ class Scanner14(scan.Scanner15):
self.genexpr_name = '<generator expression>'
return
# def ingest22(self, co, classname=None, code_objects={}, show_asm=None):
# def ingest(self, co, classname=None, code_objects={}, show_asm=None):
# tokens, customize = self.parent_ingest(co, classname, code_objects, show_asm)
# tokens = [t for t in tokens if t.kind != 'SET_LINENO']

View File

@@ -64,8 +64,13 @@ class Scanner3(Scanner):
# Ops that start SETUP_ ... We will COME_FROM with these names
# Some blocks and END_ statements. And they can start
# a new statement
if self.version < 3.8:
setup_ops = [self.opc.SETUP_LOOP, self.opc.SETUP_EXCEPT,
self.opc.SETUP_FINALLY]
self.setup_ops_no_loop = frozenset(setup_ops) - frozenset([self.opc.SETUP_LOOP])
else:
setup_ops = [self.opc.SETUP_FINALLY]
self.setup_ops_no_loop = frozenset(setup_ops)
if self.version >= 3.2:
setup_ops.append(self.opc.SETUP_WITH)
@@ -78,11 +83,9 @@ class Scanner3(Scanner):
self.pop_jump_tf = frozenset([self.opc.PJIF, self.opc.PJIT])
self.not_continue_follow = ('END_FINALLY', 'POP_BLOCK')
self.setup_ops_no_loop = frozenset(setup_ops) - frozenset([self.opc.SETUP_LOOP])
# Opcodes that can start a statement.
statement_opcodes = [
self.opc.BREAK_LOOP, self.opc.CONTINUE_LOOP,
self.opc.POP_BLOCK, self.opc.STORE_FAST,
self.opc.DELETE_FAST, self.opc.STORE_DEREF,
@@ -97,6 +100,9 @@ class Scanner3(Scanner):
self.opc.PRINT_EXPR, self.opc.JUMP_ABSOLUTE
]
if self.version < 3.8:
statement_opcodes += [self.opc.BREAK_LOOP, self.opc.CONTINUE_LOOP]
self.statement_opcodes = frozenset(statement_opcodes) | self.setup_ops_no_loop
# Opcodes that can start a "store" non-terminal.
@@ -628,7 +634,7 @@ class Scanner3(Scanner):
end = current_end
parent = struct
if op == self.opc.SETUP_LOOP:
if self.version < 3.8 and op == self.opc.SETUP_LOOP:
# We categorize loop types: 'for', 'while', 'while 1' with
# possibly suffixes '-loop' and '-else'
# Try to find the jump_back instruction of the loop.
@@ -857,11 +863,16 @@ class Scanner3(Scanner):
# if the condition jump is to a forward location.
# Also the existence of a jump to the instruction after "END_FINALLY"
# will distinguish "try/else" from "try".
if self.version < 3.8:
rtarget_break = (self.opc.RETURN_VALUE, self.opc.BREAK_LOOP)
else:
rtarget_break = (self.opc.RETURN_VALUE,)
if self.is_jump_forward(pre_rtarget) or (rtarget_is_ja and self.version >= 3.5):
if_end = self.get_target(pre_rtarget)
# If the jump target is back, we are looping
if (if_end < pre_rtarget and
if (if_end < pre_rtarget and self.version < 3.8 and
(code[prev_op[if_end]] == self.opc.SETUP_LOOP)):
if (if_end > start):
return
@@ -891,8 +902,7 @@ class Scanner3(Scanner):
'start': start,
'end': pre_rtarget})
self.not_continue.add(pre_rtarget)
elif code[pre_rtarget] in (self.opc.RETURN_VALUE,
self.opc.BREAK_LOOP):
elif code[pre_rtarget] in rtarget_break:
self.structs.append({'type': 'if-then',
'start': start,
'end': rtarget})
@@ -906,7 +916,7 @@ class Scanner3(Scanner):
# Python 3.5 may remove as dead code a JUMP
# instruction after a RETURN_VALUE. So we check
# based on seeing SETUP_EXCEPT various places.
if code[rtarget] == self.opc.SETUP_EXCEPT:
if self.version < 3.8 and code[rtarget] == self.opc.SETUP_EXCEPT:
return
# Check that next instruction after pops and jump is
# not from SETUP_EXCEPT
@@ -918,7 +928,7 @@ class Scanner3(Scanner):
if next_op in targets:
for try_op in targets[next_op]:
come_from_op = code[try_op]
if come_from_op == self.opc.SETUP_EXCEPT:
if self.version < 3.8 and come_from_op == self.opc.SETUP_EXCEPT:
return
pass
pass
@@ -957,7 +967,7 @@ class Scanner3(Scanner):
if rtarget > offset:
self.fixed_jumps[offset] = rtarget
elif op == self.opc.SETUP_EXCEPT:
elif self.version < 3.8 and op == self.opc.SETUP_EXCEPT:
target = self.get_target(offset)
end = self.restrict_to_parent(target, parent)
self.fixed_jumps[offset] = end

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2016-2018 by Rocky Bernstein
# Copyright (c) 2016-2019 by Rocky Bernstein
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,19 +18,20 @@ Python 3.7 bytecode decompiler scanner
Does some additional massaging of xdis-disassembled instructions to
make things easier for decompilation.
This sets up opcodes Python's 3.6 and calls a generalized
This sets up opcodes Python's 3.7 and calls a generalized
scanner routine for Python 3.
"""
from __future__ import print_function
from uncompyle6.scanners.scanner36 import Scanner36
from uncompyle6.scanners.scanner3 import Scanner3
# bytecode verification, verify(), uses JUMP_OPs from here
from xdis.opcodes import opcode_36 as opc
from xdis.opcodes import opcode_37 as opc
JUMP_OPs = opc.JUMP_OPS
class Scanner37(Scanner3):
class Scanner37(Scanner36):
def __init__(self, show_asm=None):
Scanner3.__init__(self, 3.7, show_asm)

View File

@@ -0,0 +1,52 @@
# Copyright (c) 2019 by Rocky Bernstein
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Python 3.8 bytecode decompiler scanner
Does some additional massaging of xdis-disassembled instructions to
make things easier for decompilation.
This sets up opcodes Python's 3.8 and calls a generalized
scanner routine for Python 3.
"""
from __future__ import print_function
from uncompyle6.scanners.scanner37 import Scanner37
from uncompyle6.scanners.scanner3 import Scanner3
# bytecode verification, verify(), uses JUMP_OPs from here
from xdis.opcodes import opcode_38 as opc
JUMP_OPs = opc.JUMP_OPS
class Scanner38(Scanner37):
def __init__(self, show_asm=None):
Scanner3.__init__(self, 3.8, show_asm)
return
pass
if __name__ == "__main__":
from uncompyle6 import PYTHON_VERSION
if PYTHON_VERSION == 3.8:
import inspect
co = inspect.currentframe().f_code
tokens, customize = Scanner38().ingest(co)
for t in tokens:
print(t.format())
pass
else:
print("Need to be Python 3.8 to demo; I am %s." %
PYTHON_VERSION)

View File

@@ -11,9 +11,10 @@ before reduction and don't reduce when there is a problem.
def checker(ast, in_loop, errors):
if ast is None:
return
in_loop = in_loop or ast.kind in ('while1stmt', 'whileTruestmt',
in_loop = (in_loop or (ast.kind in ('while1stmt', 'whileTruestmt',
'whilestmt', 'whileelsestmt', 'while1elsestmt',
'for_block')
'for_block'))
or ast.kind.startswith('async_for'))
if ast.kind in ('aug_assign1', 'aug_assign2') and ast[0][0] == 'and':
text = str(ast)
error_text = '\n# improper augmented assigment (e.g. +=, *=, ...):\n#\t' + '\n# '.join(text.split("\n")) + '\n'

Some files were not shown because too many files have changed in this diff Show More