3.8 SETUP_EXCEPT removal workaround; reinstate option -c | --compile

This commit is contained in:
rocky
2019-04-11 07:19:35 -04:00
parent cfe7feed4d
commit 7f65a8a6dd
3 changed files with 49 additions and 34 deletions

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# Mode: -*- 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> # Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# #
from __future__ import print_function from __future__ import print_function
@@ -30,7 +30,8 @@ Options:
-> /tmp/bla/fasel.pyc_dis, /tmp/bar/foo.pyc_dis -> /tmp/bla/fasel.pyc_dis, /tmp/bar/foo.pyc_dis
uncompyle6 -o /tmp /usr/lib/python1.5 uncompyle6 -o /tmp /usr/lib/python1.5
-> /tmp/smtplib.pyc_dis ... /tmp/lib-tk/FixTk.pyc_dis -> /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 -d print timestamps
-p <integer> use <integer> number of processes -p <integer> use <integer> number of processes
-r recurse directories looking for .pyc and .pyo files -r recurse directories looking for .pyc and .pyo files
@@ -43,10 +44,10 @@ Options:
--help show this message --help show this message
Debugging Options: Debugging Options:
--asm -a include byte-code (disables --verify) --asm | -a include byte-code (disables --verify)
--grammar -g show matching grammar --grammar | -g show matching grammar
--tree -t include syntax tree (disables --verify) --tree | -t include syntax tree (disables --verify)
--tree++ add template rules to --tree when possible --tree++ add template rules to --tree when possible
Extensions of generated files: Extensions of generated files:
'.pyc_dis' '.pyo_dis' successfully decompiled (and verified if --verify) '.pyc_dis' '.pyo_dis' successfully decompiled (and verified if --verify)
@@ -61,10 +62,7 @@ from uncompyle6.main import main, status_msg
from uncompyle6.version import VERSION from uncompyle6.version import VERSION
def usage(): def usage():
print("""usage: print(__doc__)
%s [--verify | --weak-verify ] [--asm] [--tree[+]] [--grammar] [-o <path>] FILE|DIR...
%s [--help | -h | --version | -V]
""" % (program, program))
sys.exit(1) sys.exit(1)
@@ -82,13 +80,13 @@ def main_bin():
numproc = 0 numproc = 0
outfile = '-' outfile = '-'
out_base = None out_base = None
codes = [] source_paths = []
timestamp = False timestamp = False
timestampfmt = "# %Y.%m.%d %H:%M:%S %Z" timestampfmt = "# %Y.%m.%d %H:%M:%S %Z"
try: try:
opts, files = getopt.getopt(sys.argv[1:], 'hagtdrVo:c:p:', opts, pyc_paths = getopt.getopt(sys.argv[1:], 'hac:gtdrVo:p:',
'help asm grammar linemaps recurse ' 'help asm compile= grammar linemaps recurse '
'timestamp tree tree+ ' 'timestamp tree tree+ '
'fragments verify verify-run version ' 'fragments verify verify-run version '
'weak-verify ' 'weak-verify '
@@ -130,8 +128,8 @@ def main_bin():
outfile = val outfile = val
elif opt in ('--timestamp', '-d'): elif opt in ('--timestamp', '-d'):
timestamp = True timestamp = True
elif opt == '-c': elif opt in ('--compile', '-c'):
codes.append(val) source_paths.append(val)
elif opt == '-p': elif opt == '-p':
numproc = int(val) numproc = int(val)
elif opt in ('--recurse', '-r'): elif opt in ('--recurse', '-r'):
@@ -143,33 +141,33 @@ def main_bin():
# expand directory if specified # expand directory if specified
if recurse_dirs: if recurse_dirs:
expanded_files = [] expanded_files = []
for f in files: for f in pyc_paths:
if os.path.isdir(f): if os.path.isdir(f):
for root, _, dir_files in os.walk(f): for root, _, dir_files in os.walk(f):
for df in dir_files: for df in dir_files:
if df.endswith('.pyc') or df.endswith('.pyo'): if df.endswith('.pyc') or df.endswith('.pyo'):
expanded_files.append(os.path.join(root, df)) expanded_files.append(os.path.join(root, df))
files = expanded_files pyc_paths = expanded_files
# argl, commonprefix works on strings, not on path parts, # argl, commonprefix works on strings, not on path parts,
# thus we must handle the case with files in 'some/classes' # thus we must handle the case with files in 'some/classes'
# and 'some/cmds' # and 'some/cmds'
src_base = os.path.commonprefix(files) src_base = os.path.commonprefix(pyc_paths)
if src_base[-1:] != os.sep: if src_base[-1:] != os.sep:
src_base = os.path.dirname(src_base) src_base = os.path.dirname(src_base)
if src_base: if src_base:
sb_len = len( os.path.join(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: if not pyc_paths and not source_paths:
print("No files given", file=sys.stderr) print("No input files given to decompile", file=sys.stderr)
usage() usage()
if outfile == '-': if outfile == '-':
outfile = None # use stdout outfile = None # use stdout
elif outfile and os.path.isdir(outfile): elif outfile and os.path.isdir(outfile):
out_base = outfile; outfile = None out_base = outfile; outfile = None
elif outfile and len(files) > 1: elif outfile and len(pyc_paths) > 1:
out_base = outfile; outfile = None out_base = outfile; outfile = None
if timestamp: if timestamp:
@@ -177,10 +175,10 @@ def main_bin():
if numproc <= 1: if numproc <= 1:
try: try:
result = main(src_base, out_base, files, None, outfile, result = main(src_base, out_base, pyc_paths, source_paths, outfile,
**options) **options)
result = list(result) + [options.get('do_verify', None)] result = list(result) + [options.get('do_verify', None)]
if len(files) > 1: if len(pyc_paths) > 1:
mess = status_msg(do_verify, *result) mess = status_msg(do_verify, *result)
print('# ' + mess) print('# ' + mess)
pass pass
@@ -196,8 +194,8 @@ def main_bin():
except ImportError: except ImportError:
from queue import Empty from queue import Empty
fqueue = Queue(len(files)+numproc) fqueue = Queue(len(pyc_paths)+numproc)
for f in files: for f in pyc_paths:
fqueue.put(f) fqueue.put(f)
for i in range(numproc): for i in range(numproc):
fqueue.put(None) 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 # 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 # 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function 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 uncompyle6 import verify, IS_PYPY, PYTHON_VERSION
from xdis.code import iscode from xdis.code import iscode
@@ -119,6 +119,22 @@ def decompile(
# deparsing failed # deparsing failed
raise pysource.SourceWalkerError(str(e)) 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, def decompile_file(filename, outstream=None, showasm=None, showast=False,
showgrammar=False, mapstream=None, do_fragments=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 # 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, showasm=None, showast=False, do_verify=False,
showgrammar=False, raise_on_error=False, showgrammar=False, raise_on_error=False,
do_linemaps=False, do_fragments=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) files list of filenames to be uncompyled (relative to in_base)
outfile write output to this filename (overwrites out_base) outfile write output to this filename (overwrites out_base)
Note: `codes` is not use. Historical compatability?
For redirecting output to For redirecting output to
- <filename> outfile=<filename> (out_base is ignored) - <filename> outfile=<filename> (out_base is ignored)
- files below out_base out_base=... - files below out_base out_base=...
@@ -171,7 +185,10 @@ def main(in_base, out_base, files, codes, outfile=None,
current_outfile = outfile current_outfile = outfile
linemap_stream = None 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) infile = os.path.join(in_base, filename)
# print("XXX", infile) # print("XXX", infile)
if not os.path.exists(infile): if not os.path.exists(infile):

View File

@@ -916,7 +916,7 @@ class Scanner3(Scanner):
# Python 3.5 may remove as dead code a JUMP # Python 3.5 may remove as dead code a JUMP
# instruction after a RETURN_VALUE. So we check # instruction after a RETURN_VALUE. So we check
# based on seeing SETUP_EXCEPT various places. # 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 return
# Check that next instruction after pops and jump is # Check that next instruction after pops and jump is
# not from SETUP_EXCEPT # not from SETUP_EXCEPT
@@ -928,7 +928,7 @@ class Scanner3(Scanner):
if next_op in targets: if next_op in targets:
for try_op in targets[next_op]: for try_op in targets[next_op]:
come_from_op = code[try_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 return
pass pass
pass pass