diff --git a/uncompyle6/__init__.py b/uncompyle6/__init__.py index c5fbf234..d0a8818c 100644 --- a/uncompyle6/__init__.py +++ b/uncompyle6/__init__.py @@ -72,14 +72,26 @@ def _load_module(filename): try: version = float(magics.versions[magic]) except KeyError: - raise ImportError("Unknown magic number %s in %s" % (ord(magic[0])+256*ord(magic[1]), filename)) - if (version > 2.7) or (version < 2.5): - raise ImportError("This is a Python %s file! Only Python 2.5 to 2.7 files are supported." % version) + raise ImportError("Unknown magic number %s in %s" % + (ord(magic[0])+256*ord(magic[1]), filename)) + if not (2.5 <= version <= 2.7) and not (version == 3.4): + raise ImportError("This is a Python %s file! Only " + "Python 2.5 to 2.7 and 3.4 files are supported." + % version) # print version fp.read(4) # timestamp if version == PYTHON_VERSION: + magic_int = magics.magic2int(magic) + # Note: a higher magic number necessarily mean a later + # release. At Pyton 3.0 the magic number decreased + # significantly. Hence the range below. Also note + # inclusion of the size info, occurred within a + # Python magor/minor release. Hence the test on the + # magic value rather than PYTHON_VERSION + if 3200 <= magic_int < 20121: + fp.read(4) # size mod 2**32 bytecode = fp.read() co = marshal.loads(bytecode) else: @@ -112,6 +124,9 @@ def uncompyle(version, co, out=None, showasm=0, showast=0): elif version == 2.5: import uncompyle6.scanners.scanner25 as scan scanner = scan.Scanner25() + elif version == 3.4: + import uncompyle6.scanners.scanner34 as scan + scanner = scan.Scanner34() scanner.setShowAsm(showasm, out) tokens, customize = scanner.disassemble(co) diff --git a/uncompyle6/disas.py b/uncompyle6/disas.py index 2aa4d18f..e25ee534 100644 --- a/uncompyle6/disas.py +++ b/uncompyle6/disas.py @@ -9,18 +9,22 @@ When the two are the same, you can simply use marshal.loads() to prodoce a code object """ -import marshal, pickle, sys, types - +import imp, marshal, pickle, sys, types import dis as Mdis from struct import unpack +import uncompyle6 +from uncompyle6.magics import magic2int + internStrings = [] # XXX For backwards compatibility disco = Mdis.disassemble PYTHON3 = (sys.version_info >= (3, 0)) +PYTHON_MAGIC_INT = magic2int(imp.get_magic()) + if PYTHON3: def long(n): return n @@ -34,7 +38,12 @@ def marshalLoad(fp): def load(fp): """ - Load marshal + marshal.load() written in Python. When the Python bytecode magic loaded is the + same magic for the running Python interpreter, we can simply use the + Python-supplied mashal.load(). + + However we need to use this when versions are different since the internal + code structures are different. Sigh. """ global internStrings @@ -56,17 +65,25 @@ def load(fp): co_name = load(fp) co_firstlineno = unpack('i', fp.read(4))[0] co_lnotab = load(fp) - if PYTHON3: - # The Python3 code object is different than Python2's which - # we are reading if we get here. In particular, it has a - # kwonlyargcount parameter which we set to 0. - # Also various parameters which were strings are now + # The Python3 code object is different than Python2's which + # we are reading if we get here. + # Also various parameters which were strings are now # bytes (which is probably more logical). - return Code(co_argcount, 0, co_nlocals, co_stacksize, co_flags, - bytes(co_code, encoding='utf-8'), - co_consts, co_names, co_varnames, co_filename, co_name, - co_firstlineno, bytes(co_lnotab, encoding='utf-8'), - co_freevars, co_cellvars) + if PYTHON3: + if PYTHON_MAGIC_INT > 3020: + # In later Python3 versions, there is a + # kwonlyargcount parameter which we set to 0. + return Code(co_argcount, 0, co_nlocals, co_stacksize, co_flags, + bytes(co_code, encoding='utf-8'), + co_consts, co_names, co_varnames, co_filename, co_name, + co_firstlineno, bytes(co_lnotab, encoding='utf-8'), + co_freevars, co_cellvars) + else: + return Code(co_argcount, 0, co_nlocals, co_stacksize, co_flags, + bytes(co_code, encoding='utf-8'), + co_consts, co_names, co_varnames, co_filename, co_name, + co_firstlineno, bytes(co_lnotab, encoding='utf-8'), + co_freevars, co_cellvars) else: return Code(co_argcount, co_nlocals, co_stacksize, co_flags, co_code, co_consts, co_names, co_varnames, co_filename, co_name, diff --git a/uncompyle6/magics.py b/uncompyle6/magics.py index 0c369660..37aec100 100755 --- a/uncompyle6/magics.py +++ b/uncompyle6/magics.py @@ -2,7 +2,7 @@ from __future__ import print_function import struct, sys -__all__ = ['magics', 'versions'] +__all__ = ['magics', 'versions', 'magics2int'] def __build_magic(magic): if (sys.version_info >= (3, 0)): @@ -10,6 +10,9 @@ def __build_magic(magic): else: return struct.pack('Hcc', magic, '\r', '\n') +def magic2int (magic): + return struct.unpack('Hcc', magic)[0] + by_magic = {} by_version = {} @@ -21,6 +24,7 @@ def __by_version(magics): versions = { # taken from from Python/import.c + # or importlib/_bootstrap.py # magic, version __build_magic(20121): '1.5', # 1.5, 1.5.1, 1.5.2 __build_magic(50428): '1.6', # 1.6 @@ -69,6 +73,21 @@ versions = { __build_magic(3160): '3.2', # 3.2a0 (add SETUP_WITH) __build_magic(3170): '3.2', # 3.2a1 (add DUP_TOP_TWO, remove DUP_TOPX and ROT_FOUR) __build_magic(3180): '3.2', # 3.2a2 (add DELETE_DEREF) + __build_magic(3190): '3.3', # Python 3.3a0 3190 __class__ super closure changed + __build_magic(3200): '3.3', # Python 3.3a0 3200 (__qualname__ added) + __build_magic(3200): '3.3', # 3210 (added size modulo 2**32 to the pyc header) + __build_magic(3220): '3.3', # Python 3.3a1 3220 (changed PEP 380 implementation) + __build_magic(3230): '3.3', # Python 3.3a4 3230 (revert changes to implicit __class__ closure) + __build_magic(3250): '3.4', # Python 3.4a1 3250 (evaluate positional default arg + # keyword-only defaults) + __build_magic(3260): '3.4', # Python 3.4a1 3260 (add LOAD_CLASSDEREF; allow locals of class to override + # free vars) + __build_magic(3270): '3.4', # Python 3.4a1 3270 (various tweaks to the __class__ closure) + __build_magic(3280): '3.4', # Python 3.4a1 3280 (remove implicit class argument) + __build_magic(3290): '3.4', # Python 3.4a4 3290 (changes to __qualname__ computation) + __build_magic(3300): '3.4', # Python 3.4a4 3300 (more changes to __qualname__ computation) + __build_magic(3310): '3.4', # Python 3.4rc2 3310 (alter __qualname__ computation) + } magics = __by_version(versions) @@ -81,7 +100,6 @@ def test(): magic_20 = magics['2.0'] current = imp.get_magic() current_version = struct.unpack('HBB', current)[0] - from trepan.api import debug; debug() magic_current = by_magic[ current ] print(type(magic_20), len(magic_20), repr(magic_20)) print() diff --git a/uncompyle6/scanners/scanner34.py b/uncompyle6/scanners/scanner34.py index 149d0e47..5c26f170 100644 --- a/uncompyle6/scanners/scanner34.py +++ b/uncompyle6/scanners/scanner34.py @@ -17,12 +17,12 @@ from uncompyle6.scanner import Token # Get all the opcodes into globals globals().update(dis.opmap) from uncompyle6.opcodes.opcode_27 import * -import scanner as scan +import uncompyle6.scanner as scan class Scanner34(scan.Scanner): def __init__(self): - self.Token = scan.Scanner.__init__(self, 2.7) # check + self.Token = scan.Scanner.__init__(self, 3.4) # check def run(self, bytecode): code_object = marshal.loads(bytecode) @@ -60,8 +60,7 @@ class Scanner34(scan.Scanner): op = code[offset] # Create token and fill all the fields we can # w/o touching arguments - current_token = Token() - current_token.type = dis.opname[op] + current_token = Token(dis.opname[op]) current_token.offset = offset current_token.linestart = True if offset in self.linestarts else False if op >= dis.HAVE_ARGUMENT: @@ -135,6 +134,16 @@ class Scanner34(scan.Scanner): for _ in range(self.op_size(op)): self.prev_op.append(offset) + def op_size(self, op): + """ + Return size of operator with its arguments + for given opcode . + """ + if op < dis.HAVE_ARGUMENT: + return 1 + else: + return 3 + def find_jump_targets(self): """ Detect all offsets in a byte code which are jump targets.