def get_opcode_module(version_info=None, variant=None): if version_info is None: version_info = sys.version_info if variant is None and IS_PYPY: variant = "pypy" pass pass elif isinstance(version_info, float): int_vers = int(version_info * 10) version_info = [int_vers // 10, int_vers % 10] vers_str = version_tuple_to_str(version_info) if len(version_info) > 3 and version_info[3] != "final": vers_str += version_tuple_to_str(version_info, start=3) if variant is None: try: import platform variant = platform.python_implementation() if platform in ("Jython", "Pyston"): vers_str += variant pass except ImportError: # Python may be too old, e.g. < 2.6 or implementation may # just not have platform pass else: vers_str += variant return op_imports[canonic_python_version[vers_str]]
def sysinfo2magic(version_info=sys.version_info): """Convert a list sys.versions_info compatible list into a 'canonic' floating-point number which that can then be used to look up a magic number. Note that this can raise an exception. """ vers_str = version_tuple_to_str(version_info) if version_info[3] != "final": vers_str += version_tuple_to_str(version_info, start=3) if IS_PYPY: vers_str += "pypy" else: try: import platform platform = platform.python_implementation() if platform in ("Jython", "Pyston", "GraalVM"): vers_str += platform pass except ImportError: # Python may be too old, e.g. < 2.6 or implementation may # just not have platform pass return magics[vers_str]
def compile_file(source_path: str) -> str: 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, version_tuple_to_str()) else: bytecode_path = "%s-%s.pyc" % (basename, version_tuple_to_str()) print("compiling %s to %s" % (source_path, bytecode_path)) py_compile.compile(source_path, bytecode_path, "exec") return bytecode_path
def get_scanner(version, is_pypy=False, show_asm=None): # If version is a string, turn that into the corresponding float. if isinstance(version, str): if version not in canonic_python_version: raise RuntimeError("Unknown Python version in xdis %s" % version) canonic_version = canonic_python_version[version] if canonic_version not in CANONIC2VERSION: raise RuntimeError("Unsupported Python version %s (canonic %s)" % (version, canonic_version)) version = CANONIC2VERSION[canonic_version] # Pick up appropriate scanner if version[:2] in PYTHON_VERSIONS: v_str = version_tuple_to_str(version, start=0, end=2, delimiter="") try: import importlib if is_pypy: scan = importlib.import_module("uncompyle6.scanners.pypy%s" % v_str) else: scan = importlib.import_module( "uncompyle6.scanners.scanner%s" % v_str) if False: print(scan) # Avoid unused scan except ImportError: if is_pypy: exec( "import uncompyle6.scanners.pypy%s as scan" % v_str, locals(), globals(), ) else: exec( "import uncompyle6.scanners.scanner%s as scan" % v_str, locals(), globals(), ) if is_pypy: scanner = eval("scan.ScannerPyPy%s(show_asm=show_asm)" % v_str, locals(), globals()) else: scanner = eval("scan.Scanner%s(show_asm=show_asm)" % v_str, locals(), globals()) else: raise RuntimeError( "Unsupported Python version, %s, for decompilation" % version_tuple_to_str(version)) return scanner
def to_native(self): if not (PYTHON_VERSION_TRIPLE >= (3, 8)): raise TypeError( "Python Interpreter needs to be in 3.8 or greater; is %s" % version_tuple_to_str() ) code = deepcopy(self) code.freeze() try: code.check() except AssertionError as e: raise TypeError(e) return types.CodeType( code.co_argcount, code.co_posonlyargcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, code.co_consts, code.co_names, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars, )
def test_basic(self): """Basic test of magic numbers""" if hasattr(sys, 'version_info'): version = version_tuple_to_str() if IS_PYPY: version += 'pypy' self.assertTrue( version in magics.magics.keys(), "version %s is not in magic.magics.keys: %s" % (version, magics.magics.keys())) self.assertEqual(magics.MAGIC, magics.int2magic(magics.magic2int(magics.MAGIC))) lookup = str(PYTHON_VERSION) if IS_PYPY: lookup += 'pypy' self.assertTrue( lookup in magics.magics.keys(), "PYTHON VERSION %s is not in magic.magics.keys: %s" % (lookup, magics.magics.keys())) if not (3, 5, 2) <= sys.version_info < (3, 6, 0): self.assertEqual( magics.sysinfo2magic(), magics.MAGIC, "magic from imp.get_magic() for %s " "should be sysinfo2magic()" % lookup)
def to_native(self, opts={}): if not (2, 0) <= PYTHON_VERSION_TRIPLE < (2, 8): raise TypeError( "Python Interpreter needs to be in range 2.0..2.7; is %s" % version_tuple_to_str()) code = deepcopy(self) code.freeze() try: code.check() except AssertionError as e: raise TypeError(e) return types.CodeType( code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, code.co_consts, code.co_names, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars, )
def __init__(self, version, show_asm=None, is_pypy=False): self.version = version self.show_asm = show_asm self.is_pypy = is_pypy if version[:2] in PYTHON_VERSIONS: v_str = f"""opcode_{version_tuple_to_str(version, start=0, end=2, delimiter="")}""" if is_pypy: v_str += "pypy" exec("from xdis.opcodes import %s" % v_str) exec("self.opc = %s" % v_str) else: raise TypeError("%s is not a Python version I know about" % version_tuple_to_str(version)) self.opname = self.opc.opname # FIXME: This weird Python2 behavior is not Python3 self.resetTokenClass()
def write_pycfile(fp, code_list, timestamp=None, version_triple=xdis.PYTHON_VERSION_TRIPLE): version_str = version_tuple_to_str(version_triple, end=2) magic_bytes = magics[version_str] magic_int = magic2int(magic_bytes) fp.write(magic_bytes) if timestamp is None: timestamp = int(time.time()) write_source_size = version_triple >= (3, 3) if version_triple >= (3, 7): if magic_int == 3393: fp.write(pack("I", timestamp)) fp.write(pack("I", 0)) else: # PEP 552. https://www.python.org/dev/peps/pep-0552/ # 0 in the lowest-order bit means used old-style timestamps fp.write(pack("<I", 0)) fp.write(pack("<I", timestamp)) else: fp.write(pack("<I", timestamp)) if write_source_size: fp.write(pack("<I", 0)) # size mod 2**32 for co in code_list: try: co_obj = dumps(co, python_version=version_triple) if PYTHON3 and version_triple < (3, 0): co_obj = str.encode(co_obj) pass fp.write(co_obj) except: # noqa pass
def test_if_in_for(): code = bug.__code__ scan = get_scanner(PYTHON_VERSION_TRIPLE) if (2, 7) <= PYTHON_VERSION_TRIPLE < (3, 1) and not IS_PYPY: scan.build_instructions(code) fjt = scan.find_jump_targets(False) ## FIXME: the data below is wrong. ## we get different results currenty as well. ## We need to probably fix both the code ## and the test below # assert {15: [3], 69: [66], 63: [18]} == fjt # assert scan.structs == \ # [{'start': 0, 'end': 72, 'type': 'root'}, # {'start': 15, 'end': 66, 'type': 'if-then'}, # {'start': 31, 'end': 59, 'type': 'for-loop'}, # {'start': 62, 'end': 63, 'type': 'for-else'}] code = bug_loop.__code__ scan.build_instructions(code) fjt = scan.find_jump_targets(False) assert {64: [42], 67: [42, 42], 42: [16, 41], 19: [6]} == fjt assert scan.structs == [ { 'start': 0, 'end': 80, 'type': 'root' }, { 'start': 3, 'end': 64, 'type': 'if-then' }, { 'start': 6, 'end': 15, 'type': 'try' }, { 'start': 19, 'end': 38, 'type': 'except' }, { 'start': 45, 'end': 67, 'type': 'while-loop' }, { 'start': 70, 'end': 64, 'type': 'while-else' }, # previous bug was not mistaking while-loop for if-then { 'start': 48, 'end': 67, 'type': 'while-loop' } ] elif (3, 2) < PYTHON_VERSION_TRIPLE <= (3, 4): scan.build_instructions(code) fjt = scan.find_jump_targets(False) assert {69: [66], 63: [18]} == fjt assert scan.structs == \ [{'end': 72, 'type': 'root', 'start': 0}, {'end': 66, 'type': 'if-then', 'start': 6}, {'end': 63, 'type': 'if-then', 'start': 18}, {'end': 59, 'type': 'for-loop', 'start': 31}, {'end': 63, 'type': 'for-else', 'start': 62}] else: print("FIXME: should fix for %s" % version_tuple_to_str()) assert True return
self.opc.STORE_SLICE_1, self.opc.STORE_SLICE_2, self.opc.STORE_SLICE_3, self.opc.STORE_SUBSCR, self.opc.UNPACK_SEQUENCE, self.opc.JUMP_ABSOLUTE, ]) self.pop_jump_if_or_pop = frozenset( [self.opc.JUMP_IF_FALSE_OR_POP, self.opc.JUMP_IF_TRUE_OR_POP]) return pass if __name__ == "__main__": from xdis.version_info import PYTHON_VERSION_TRIPLE if PYTHON_VERSION_TRIPLE[:2] == (2, 7): import inspect co = inspect.currentframe().f_code tokens, customize = Scanner27().ingest(co) for t in tokens: print(t) pass else: print("Need to be Python 2.7 to demo; I am %s." % version_tuple_to_str())
def decompile( bytecode_version: str, co, out=None, showasm=None, showast={}, timestamp=None, showgrammar=False, source_encoding=None, code_objects={}, source_size=None, is_pypy=None, magic_int=None, mapstream=None, do_fragments=False, ) -> Any: """ ingests and deparses a given code block 'co' if `bytecode_version` is None, use the current Python intepreter version. Caller is responsible for closing `out` and `mapstream` """ if bytecode_version is None: bytecode_version = PYTHON_VERSION_TRIPLE # store final output stream for case of error real_out = out or sys.stdout def write(s): s += "\n" real_out.write(s) assert iscode(co), f"""{co} does not smell like code""" co_pypy_str = "PyPy " if is_pypy else "" run_pypy_str = "PyPy " if IS_PYPY else "" sys_version_lines = sys.version.split("\n") if source_encoding: write("# -*- coding: %s -*-" % source_encoding) write("# uncompyle6 version %s\n" "# %sPython bytecode %s%s\n# Decompiled from: %sPython %s" % ( __version__, co_pypy_str, version_tuple_to_str(bytecode_version), " (%s)" % str(magic_int) if magic_int else "", run_pypy_str, "\n# ".join(sys_version_lines), )) if co.co_filename: write("# Embedded file name: %s" % co.co_filename) if timestamp: write("# Compiled at: %s" % datetime.datetime.fromtimestamp(timestamp)) if source_size: write("# Size of source mod 2**32: %d bytes" % source_size) debug_opts = {"asm": showasm, "ast": showast, "grammar": showgrammar} try: if mapstream: if isinstance(mapstream, str): mapstream = _get_outstream(mapstream) deparsed = deparse_code_with_map( co, out, bytecode_version, debug_opts, code_objects=code_objects, is_pypy=is_pypy, ) header_count = 3 + len(sys_version_lines) linemap = [(line_no, deparsed.source_linemap[line_no] + header_count) for line_no in sorted(deparsed.source_linemap.keys())] mapstream.write("\n\n# %s\n" % linemap) else: if do_fragments: deparse_fn = code_deparse_fragments else: deparse_fn = code_deparse deparsed = deparse_fn(co, out, bytecode_version, debug_opts=debug_opts, is_pypy=is_pypy) pass return deparsed except pysource.SourceWalkerError as e: # deparsing failed raise pysource.SourceWalkerError(str(e))
#!/usr/bin/env python """ Trivial helper program to bytecompile """ from xdis.version_info import version_tuple_to_str import os, sys, py_compile assert len(sys.argv) == 2 path = sys.argv[1] short = os.path.basename(path) version = version_tuple_to_str(end=2) if hasattr(sys, 'pypy_version_info'): cfile = "bytecode_pypy%s/%s" % (version, short) + 'c' else: cfile = "bytecode_%s/%s" % (version, short) + 'c' print("byte-compiling %s to %s" % (path, cfile)) py_compile.compile(path, cfile) os.system("../bin/pydisasm %s" % cfile)
(2, 5), (2, 6), (2, 7), (3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), )) CANONIC2VERSION = dict( (canonic_python_version[version_tuple_to_str(python_version)], python_version) for python_version in PYTHON_VERSIONS) # Magic changed mid version for Python 3.5.2. Compatibility was added for # the older 3.5 interpreter magic. CANONIC2VERSION["3.5.2"] = 3.5 # FIXME: DRY if PYTHON3: intern = sys.intern L65536 = 65536 def long(num): return num else: