diff --git a/__pkginfo__.py b/__pkginfo__.py index 97b42ee3..d64e9d62 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -30,30 +30,27 @@ classifiers = ['Development Status :: 4 - Beta', # The rest in alphabetic order author = "Rocky Bernstein, Hartmut Goebel, John Aycock, and others" author_email = "rb@dustyfeet.com" +entry_points={ + 'console_scripts': [ + 'uncompyle6=bin.uncompile6:main', + 'pydisassemble=bin.pydisassemble:main', + ]} ftp_url = None install_requires = ['spark-parser >= 1.1.1'] license = 'MIT' mailing_list = 'python-debugger@googlegroups.com' modname = 'uncompyle6' -packages = ['uncompyle6', 'uncompyle6.opcodes', 'uncompyle6.semantics', 'uncompyle6.scanners', 'uncompyle6.parsers'] +packages = ['uncompyle6', 'uncompyle6.opcodes', 'uncompyle6.semantics', 'uncompyle6.scanners', + 'uncompyle6.parsers'] py_modules = None short_desc = 'Python byte-code disassembler and source-code converter' -scripts = ['bin/uncompyle6', 'bin/pydisassemble'] - -import os.path - - -def get_srcdir(): - filename = os.path.normcase(os.path.dirname(os.path.abspath(__file__))) - return os.path.realpath(filename) - -ns = {} web = 'https://github.com/rocky/python-uncompyle6/' # tracebacks in zip files are funky and not debuggable zip_safe = True +import os.path def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() diff --git a/bin/pydisassemble b/bin/pydisassemble index cb220cd5..9493e056 100755 --- a/bin/pydisassemble +++ b/bin/pydisassemble @@ -1,88 +1,3 @@ #!/usr/bin/env python -# Mode: -*- python -*- -# -# Copyright (c) 2015-2016 by Rocky Bernstein -# -from __future__ import print_function -import sys, os, getopt - -program = os.path.basename(__file__) - -__doc__ = """ -Usage: - %s [OPTIONS]... FILE - %s [--help | -h | -V | --version] - -Examples: - %s foo.pyc - %s foo.py - %s -o foo.pydis foo.pyc - %s -o /tmp foo.pyc - -Options: - -o output decompiled files to this path: - if multiple input files are decompiled, the common prefix - is stripped from these names and the remainder appended to - - --help show this message - -""" % ((program,) * 6) - - -Usage_short = \ -"%s [--help] [--verify] [--showasm] [--showast] [-o ] FILE|DIR..." % program - -from uncompyle6 import check_python_version -from uncompyle6.disas import disassemble_files -from uncompyle6.version import VERSION - -check_python_version(program) - -outfile = '-' -out_base = None - -if len(sys.argv) == 1: - print("No file(s) or directory given", file=sys.stderr) - print(Usage_short, file=sys.stderr) - sys.exit(1) - -try: - opts, files = getopt.getopt(sys.argv[1:], 'hVo:', ['help', 'version']) -except getopt.GetoptError as e: - print('%s: %s' % (os.path.basename(sys.argv[0]), e), file=sys.stderr) - sys.exit(-1) - -for opt, val in opts: - if opt in ('-h', '--help'): - print(__doc__) - sys.exit(1) - elif opt in ('-V', '--version'): - print("%s %s" % (program, VERSION)) - sys.exit(0) - elif opt == '-o': - outfile = val - else: - print(opt) - print(Usage_short, file=sys.stderr) - sys.exit(1) - - -# 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) -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] - del sb_len - -if outfile == '-': - outfile = None # use stdout -elif outfile and os.path.isdir(outfile): - out_base = outfile; outfile = None -elif outfile and len(files) > 1: - out_base = outfile; outfile = None - -disassemble_files(src_base, out_base, files, outfile) +from uncompyle6.bin.pydisassemble import main +main() diff --git a/bin/uncompyle6 b/bin/uncompyle6 index 4eac2e85..8c752b1e 100755 --- a/bin/uncompyle6 +++ b/bin/uncompyle6 @@ -1,222 +1,3 @@ #!/usr/bin/env python -# Mode: -*- python -*- -# -# Copyright (c) 2015-2016 by Rocky Bernstein -# Copyright (c) 2000-2002 by hartmut Goebel -# -from __future__ import print_function -import sys, os, getopt, time - -program = os.path.basename(__file__) - -__doc__ = """ -Usage: - %s [OPTIONS]... [ FILE | DIR]... - %s [--help | -h | --V | --version] - -Examples: - %s foo.pyc bar.pyc # decompile foo.pyc, bar.pyc to stdout - %s -o . foo.pyc bar.pyc # decompile to ./foo.pyc_dis and ./bar.pyc_dis - %s -o /tmp /usr/lib/python1.5 # decompile whole library - -Options: - -o output decompiled files to this path: - if multiple input files are decompiled, the common prefix - is stripped from these names and the remainder appended to - - uncompyle6 -o /tmp bla/fasel.pyc bla/foo.pyc - -> /tmp/fasel.pyc_dis, /tmp/foo.pyc_dis - uncompyle6 -o /tmp bla/fasel.pyc bar/foo.pyc - -> /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 - -d print timestamps - -p use number of processes - -r recurse directories looking for .pyc and .pyo files - --verify compare generated source with input byte-code - (requires -o) - --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) - -Extensions of generated files: - '.pyc_dis' '.pyo_dis' successfully decompiled (and verified if --verify) - + '_unverified' successfully decompile but --verify failed - + '_failed' decompile failed (contact author for enhancement) -""" % ((program,) * 5) - -program = os.path.basename(__file__) - -from uncompyle6 import verify, check_python_version -from uncompyle6.main import main, status_msg -from uncompyle6.version import VERSION - -def usage(): - print("""usage: - %s [--verify] [--asm] [--tree] [--grammar] [-o ] FILE|DIR... - %s [--help | -h | --version | -V] -""" % (program, program)) - sys.exit(1) - - -check_python_version(program) - -showasm = showast = do_verify = recurse_dirs = False -numproc = 0 -outfile = '-' -out_base = None -codes = [] -timestamp = False -timestampfmt = "# %Y.%m.%d %H:%M:%S %Z" - -try: - opts, files = getopt.getopt(sys.argv[1:], 'hagtdrVo:c:p:', - 'help asm grammar recurse timestamp tree verify version ' - 'showgrammar'.split(' ')) -except getopt.GetoptError as e: - print('%s: %s' % (os.path.basename(sys.argv[0]), e), file=sys.stderr) - sys.exit(-1) - -options = {} -for opt, val in opts: - if opt in ('-h', '--help'): - print(__doc__) - sys.exit(0) - elif opt in ('-V', '--version'): - print("%s %s" % (program, VERSION)) - sys.exit(0) - elif opt == '--verify': - options['do_verify'] = True - elif opt in ('--asm', '-a'): - options['showasm'] = True - options['do_verify'] = False - elif opt in ('--tree', '-t'): - options['showast'] = True - options['do_verify'] = False - elif opt in ('--grammar', '-g'): - options['showgrammar'] = True - elif opt == '-o': - outfile = val - elif opt in ('--timestamp', '-d'): - timestamp = True - elif opt == '-c': - codes.append(val) - elif opt == '-p': - numproc = int(val) - elif opt in ('--recurse', '-r'): - recurse_dirs = True - else: - print(opt, file=sys.stderr) - usage() - -# expand directory if specified -if recurse_dirs: - expanded_files = [] - for f in files: - 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 - -# 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) -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] - del sb_len - -if not files: - print("No files given", 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: - out_base = outfile; outfile = None - -if timestamp: - print(time.strftime(timestampfmt)) - -if numproc <= 1: - try: - result = main(src_base, out_base, files, codes, outfile, - **options) - if len(files) > 1: - mess = status_msg(do_verify, *result) - print('# ' + mess) - pass - except (KeyboardInterrupt): - pass - except verify.VerifyCmpError: - raise -else: - from multiprocessing import Process, Queue - - try: - from Queue import Empty - except ImportError: - from Queue import Empty - - fqueue = Queue(len(files)+numproc) - for f in files: - fqueue.put(f) - for i in range(numproc): - fqueue.put(None) - - rqueue = Queue(numproc) - - def process_func(): - try: - (tot_files, okay_files, failed_files, verify_failed_files) = (0, 0, 0, 0) - while 1: - f = fqueue.get() - if f is None: - break - (t, o, f, v) = \ - main(src_base, out_base, [f], codes, outfile, **options) - tot_files += t - okay_files += o - failed_files += f - verify_failed_files += v - except (Empty, KeyboardInterrupt): - pass - rqueue.put((tot_files, okay_files, failed_files, verify_failed_files)) - rqueue.close() - - try: - procs = [Process(target=process_func) for i in range(numproc)] - for p in procs: - p.start() - for p in procs: - p.join() - try: - (tot_files, okay_files, failed_files, verify_failed_files) = (0, 0, 0, 0) - while True: - (t, o, f, v) = rqueue.get(False) - tot_files += t - okay_files += o - failed_files += f - verify_failed_files += v - except Empty: - pass - print('# decompiled %i files: %i okay, %i failed, %i verify failed' % - (tot_files, okay_files, failed_files, verify_failed_files)) - except (KeyboardInterrupt, OSError): - pass - - -if timestamp: - print(time.strftime(timestampfmt)) +from uncompyle6.bin.uncompile import main_bin +main_bin() diff --git a/setup.py b/setup.py index b0319c13..e7ffe0cf 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from __pkginfo__ import \ author, author_email, install_requires, \ license, long_description, classifiers, \ - modname, packages, py_modules, scripts, \ + modname, packages, py_modules, entry_points, \ short_desc, web, zip_safe from setuptools import setup @@ -17,6 +17,7 @@ setup( author_email = author_email, classifiers = classifiers, description = short_desc, + entry_points = entry_points, install_requires = install_requires, license = license, long_description = long_description, @@ -26,6 +27,5 @@ setup( test_suite = 'nose.collector', url = web, setup_requires = ['nose>=1.0'], - scripts = scripts, version = VERSION, zip_safe = zip_safe) diff --git a/test/simple_source/bug26/05-ret-or.py b/test/simple_source/bug26/05-ret-or.py new file mode 100644 index 00000000..be00294a --- /dev/null +++ b/test/simple_source/bug26/05-ret-or.py @@ -0,0 +1,9 @@ +# Python 2.6 +# In contrast to Python 2.7 there might be no "COME_FROM" so we add rule: +# ret_or ::= expr JUMP_IF_TRUE expr +# where Python 2.7 has +# ret_or ::= expr JUMP_IF_TRUE expr COME_FROM + +class BufferedIncrementalEncoder(object): + def getstate(self): + return self.buffer or 0 diff --git a/test/simple_source/def/03_star_arg.py b/test/simple_source/def/03_star_arg.py new file mode 100644 index 00000000..8fc29890 --- /dev/null +++ b/test/simple_source/def/03_star_arg.py @@ -0,0 +1,2 @@ +def main(args=None, *, wrap_timer=None): + return 5 diff --git a/uncompyle6/bin/__init__.py b/uncompyle6/bin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/uncompyle6/bin/pydisassemble.py b/uncompyle6/bin/pydisassemble.py new file mode 100755 index 00000000..b15deb99 --- /dev/null +++ b/uncompyle6/bin/pydisassemble.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# Mode: -*- python -*- +# +# Copyright (c) 2015-2016 by Rocky Bernstein +# +from __future__ import print_function +import sys, os, getopt + +program = os.path.basename(__file__) + +__doc__ = """ +Usage: + %s [OPTIONS]... FILE + %s [--help | -h | -V | --version] + +Examples: + %s foo.pyc + %s foo.py + %s -o foo.pydis foo.pyc + %s -o /tmp foo.pyc + +Options: + -o output decompiled files to this path: + if multiple input files are decompiled, the common prefix + is stripped from these names and the remainder appended to + + --help show this message + +""" % ((program,) * 6) + +def main(): + Usage_short = \ + "%s [--help] [--verify] [--showasm] [--showast] [-o ] FILE|DIR..." % program + + from uncompyle6 import check_python_version + from uncompyle6.disas import disassemble_files + from uncompyle6.version import VERSION + + check_python_version(program) + + outfile = '-' + out_base = None + + if len(sys.argv) == 1: + print("No file(s) or directory given", file=sys.stderr) + print(Usage_short, file=sys.stderr) + sys.exit(1) + + try: + opts, files = getopt.getopt(sys.argv[1:], 'hVo:', ['help', 'version']) + except getopt.GetoptError as e: + print('%s: %s' % (os.path.basename(sys.argv[0]), e), file=sys.stderr) + sys.exit(-1) + + for opt, val in opts: + if opt in ('-h', '--help'): + print(__doc__) + sys.exit(1) + elif opt in ('-V', '--version'): + print("%s %s" % (program, VERSION)) + sys.exit(0) + elif opt == '-o': + outfile = val + else: + print(opt) + print(Usage_short, file=sys.stderr) + sys.exit(1) + + + # 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) + 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] + del sb_len + + if outfile == '-': + outfile = None # use stdout + elif outfile and os.path.isdir(outfile): + out_base = outfile; outfile = None + elif outfile and len(files) > 1: + out_base = outfile; outfile = None + + disassemble_files(src_base, out_base, files, outfile) + return + +if __name__ == '__main__': + main() diff --git a/uncompyle6/bin/uncompile.py b/uncompyle6/bin/uncompile.py new file mode 100755 index 00000000..745272bd --- /dev/null +++ b/uncompyle6/bin/uncompile.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python +# Mode: -*- python -*- +# +# Copyright (c) 2015-2016 by Rocky Bernstein +# Copyright (c) 2000-2002 by hartmut Goebel +# +from __future__ import print_function +import sys, os, getopt, time + +program = os.path.basename(__file__) + +__doc__ = """ +Usage: + %s [OPTIONS]... [ FILE | DIR]... + %s [--help | -h | --V | --version] + +Examples: + %s foo.pyc bar.pyc # decompile foo.pyc, bar.pyc to stdout + %s -o . foo.pyc bar.pyc # decompile to ./foo.pyc_dis and ./bar.pyc_dis + %s -o /tmp /usr/lib/python1.5 # decompile whole library + +Options: + -o output decompiled files to this path: + if multiple input files are decompiled, the common prefix + is stripped from these names and the remainder appended to + + uncompyle6 -o /tmp bla/fasel.pyc bla/foo.pyc + -> /tmp/fasel.pyc_dis, /tmp/foo.pyc_dis + uncompyle6 -o /tmp bla/fasel.pyc bar/foo.pyc + -> /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 + -d print timestamps + -p use number of processes + -r recurse directories looking for .pyc and .pyo files + --verify compare generated source with input byte-code + (requires -o) + --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) + +Extensions of generated files: + '.pyc_dis' '.pyo_dis' successfully decompiled (and verified if --verify) + + '_unverified' successfully decompile but --verify failed + + '_failed' decompile failed (contact author for enhancement) +""" % ((program,) * 5) + +program = os.path.basename(__file__) + +from uncompyle6 import verify, check_python_version +from uncompyle6.main import main, status_msg +from uncompyle6.version import VERSION + +def usage(): + print("""usage: + %s [--verify] [--asm] [--tree] [--grammar] [-o ] FILE|DIR... + %s [--help | -h | --version | -V] +""" % (program, program)) + sys.exit(1) + + +def main_bin(): + check_python_version(program) + + showasm = showast = do_verify = recurse_dirs = False + numproc = 0 + outfile = '-' + out_base = None + codes = [] + timestamp = False + timestampfmt = "# %Y.%m.%d %H:%M:%S %Z" + + try: + opts, files = getopt.getopt(sys.argv[1:], 'hagtdrVo:c:p:', + 'help asm grammar recurse timestamp tree verify version ' + 'showgrammar'.split(' ')) + except getopt.GetoptError as e: + print('%s: %s' % (os.path.basename(sys.argv[0]), e), file=sys.stderr) + sys.exit(-1) + + options = {} + for opt, val in opts: + if opt in ('-h', '--help'): + print(__doc__) + sys.exit(0) + elif opt in ('-V', '--version'): + print("%s %s" % (program, VERSION)) + sys.exit(0) + elif opt == '--verify': + options['do_verify'] = True + elif opt in ('--asm', '-a'): + options['showasm'] = True + options['do_verify'] = False + elif opt in ('--tree', '-t'): + options['showast'] = True + options['do_verify'] = False + elif opt in ('--grammar', '-g'): + options['showgrammar'] = True + elif opt == '-o': + outfile = val + elif opt in ('--timestamp', '-d'): + timestamp = True + elif opt == '-c': + codes.append(val) + elif opt == '-p': + numproc = int(val) + elif opt in ('--recurse', '-r'): + recurse_dirs = True + else: + print(opt, file=sys.stderr) + usage() + + # expand directory if specified + if recurse_dirs: + expanded_files = [] + for f in files: + 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 + + # 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) + 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] + del sb_len + + if not files: + print("No files given", 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: + out_base = outfile; outfile = None + + if timestamp: + print(time.strftime(timestampfmt)) + + if numproc <= 1: + try: + result = main(src_base, out_base, files, codes, outfile, + **options) + if len(files) > 1: + mess = status_msg(do_verify, *result) + print('# ' + mess) + pass + except (KeyboardInterrupt): + pass + except verify.VerifyCmpError: + raise + else: + from multiprocessing import Process, Queue + + try: + from Queue import Empty + except ImportError: + from Queue import Empty + + fqueue = Queue(len(files)+numproc) + for f in files: + fqueue.put(f) + for i in range(numproc): + fqueue.put(None) + + rqueue = Queue(numproc) + + def process_func(): + try: + (tot_files, okay_files, failed_files, verify_failed_files) = (0, 0, 0, 0) + while 1: + f = fqueue.get() + if f is None: + break + (t, o, f, v) = \ + main(src_base, out_base, [f], codes, outfile, **options) + tot_files += t + okay_files += o + failed_files += f + verify_failed_files += v + except (Empty, KeyboardInterrupt): + pass + rqueue.put((tot_files, okay_files, failed_files, verify_failed_files)) + rqueue.close() + + try: + procs = [Process(target=process_func) for i in range(numproc)] + for p in procs: + p.start() + for p in procs: + p.join() + try: + (tot_files, okay_files, failed_files, verify_failed_files) = (0, 0, 0, 0) + while True: + (t, o, f, v) = rqueue.get(False) + tot_files += t + okay_files += o + failed_files += f + verify_failed_files += v + except Empty: + pass + print('# decompiled %i files: %i okay, %i failed, %i verify failed' % + (tot_files, okay_files, failed_files, verify_failed_files)) + except (KeyboardInterrupt, OSError): + pass + + + if timestamp: + print(time.strftime(timestampfmt)) + + return + +if __name__ == '__main__': + main_bin()