diff --git a/uncompyle6/bin/uncompile.py b/uncompyle6/bin/uncompile.py index c482bcbd..b9f5c014 100755 --- a/uncompyle6/bin/uncompile.py +++ b/uncompyle6/bin/uncompile.py @@ -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 # 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 attempts a disassembly after compiling + --compile | -c + attempts a decompilation after compiling -d print timestamps -p use number of processes -r recurse directories looking for .pyc and .pyo files @@ -43,10 +44,10 @@ 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) - --tree++ add template rules to --tree when possible + --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: '.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 def usage(): - print("""usage: - %s [--verify | --weak-verify ] [--asm] [--tree[+]] [--grammar] [-o ] FILE|DIR... - %s [--help | -h | --version | -V] -""" % (program, program)) + print(__doc__) 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) diff --git a/uncompyle6/main.py b/uncompyle6/main.py index a5a0daf7..c6e0ecea 100644 --- a/uncompyle6/main.py +++ b/uncompyle6/main.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018 Rocky Bernstein +# Copyright (C) 2018-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 @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . 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 - outfile= (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): diff --git a/uncompyle6/scanners/scanner3.py b/uncompyle6/scanners/scanner3.py index 693a6d0f..7b0b328f 100644 --- a/uncompyle6/scanners/scanner3.py +++ b/uncompyle6/scanners/scanner3.py @@ -916,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 @@ -928,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