""" Decompiler for Python3.3. Decompile a module or a function using the decompile() function >>> from unpyc3 import decompile >>> def foo(x, y, z=3, *args): ... global g ... for i, j in zip(x, y): ... if z == i + j or args[i] == j: ... g = i, j ... return ... >>> print(decompile(foo)) def foo(x, y, z=3, *args): global g for i, j in zip(x, y): if z == i + j or args[i] == j: g = i, j return >>> """ __all__ = ['decompile'] # TODO: # - Support for keyword-only arguments # - Handle assert statements better # - (Partly done) Nice spacing between function/class declarations import dis from array import array from opcode import opname, opmap, HAVE_ARGUMENT, cmp_op import imp import inspect # Masks for code object's co_flag attribute VARARGS = 4 VARKEYWORDS = 8 # Put opcode names in the global namespace for name, val in opmap.items(): globals()[name] = val # These opcodes will generate a statement. This is used in the first # pass (in Code.find_else) to find which POP_JUMP_IF_* instructions # are jumps to the else clause of an if statement stmt_opcodes = { SETUP_LOOP, BREAK_LOOP, CONTINUE_LOOP, SETUP_FINALLY, END_FINALLY, SETUP_EXCEPT, POP_EXCEPT, SETUP_WITH, POP_BLOCK, STORE_FAST, DELETE_FAST, STORE_DEREF, DELETE_DEREF, STORE_GLOBAL, DELETE_GLOBAL, STORE_NAME, DELETE_NAME, STORE_ATTR, DELETE_ATTR, IMPORT_NAME, IMPORT_FROM, RETURN_VALUE, YIELD_VALUE, RAISE_VARARGS, POP_TOP, } # Conditional branching opcode that make up if statements and and/or # expressions pop_jump_if_opcodes = (POP_JUMP_IF_TRUE, POP_JUMP_IF_FALSE) # These opcodes indicate that a pop_jump_if_x to the address just # after them is an else-jump else_jump_opcodes = ( JUMP_FORWARD, RETURN_VALUE, JUMP_ABSOLUTE, SETUP_LOOP, RAISE_VARARGS ) # These opcodes indicate for loop rather than while loop for_jump_opcodes = ( GET_ITER, FOR_ITER ) def read_code(stream): # This helper is needed in order for the PEP 302 emulation to # correctly handle compiled files # Note: stream must be opened in "rb" mode import marshal magic = stream.read(4) if magic != imp.get_magic(): print("*** Warning: file has wrong magic number ***") stream.read(4) # Skip timestamp stream.read(4) # Skip rawsize return marshal.load(stream) def dec_module(path): if path.endswith(".py"): path = imp.cache_from_source(path) elif not path.endswith(".pyc") and not path.endswith(".pyo"): raise ValueError("path must point to a .py or .pyc file") stream = open(path, "rb") code_obj = read_code(stream) code = Code(code_obj) return code.get_suite(include_declarations=False, look_for_docstring=True) def decompile(obj): """ Decompile obj if it is a module object, a function or a code object. If obj is a string, it is assumed to be the path to a python module. """ if isinstance(obj, str): return dec_module(obj) if inspect.iscode(obj): code = Code(obj) return code.get_suite() if inspect.isfunction(obj): code = Code(obj.__code__) defaults = obj.__defaults__ kwdefaults = obj.__kwdefaults__ return DefStatement(code, defaults, kwdefaults, obj.__closure__) elif inspect.ismodule(obj): return dec_module(obj.__file__) else: msg = "Object must be string, module, function or code object" raise TypeError(msg) class Indent: def __init__(self, indent_level=0, indent_step=4): self.level = indent_level self.step = indent_step def write(self, pattern, *args, **kwargs): if args or kwargs: pattern = pattern.format(*args, **kwargs) return self.indent(pattern) def __add__(self, indent_increase): return type(self)(self.level + indent_increase, self.step) class IndentPrint(Indent): def indent(self, string): print(" " * self.step * self.level + string) class IndentString(Indent): def __init__(self, indent_level=0, indent_step=4, lines=None): Indent.__init__(self, indent_level, indent_step) if lines is None: self.lines = [] else: self.lines = lines def __add__(self, indent_increase): return type(self)(self.level + indent_increase, self.step, self.lines) def sep(self): if not self.lines or self.lines[-1]: self.lines.append("") def indent(self, string): self.lines.append(" " * self.step * self.level + string) def __str__(self): return "\n".join(self.lines) class Stack: def __init__(self): self._stack = [] self._counts = {} def __bool__(self): return bool(self._stack) def __len__(self): return len(self._stack) def __contains__(self, val): return self.get_count(val) > 0 def get_count(self, obj): return self._counts.get(id(obj), 0) def set_count(self, obj, val): if val: self._counts[id(obj)] = val else: del self._counts[id(obj)] def pop1(self): val = self._stack.pop() if self._stack else PyConst('ERROR') self.set_count(val, self.get_count(val) - 1) return val def pop(self, count=None): if count is None: return self.pop1() else: vals = [self.pop1() for i in range(count)] vals.reverse() return vals def push(self, *args): for val in args: self.set_count(val, self.get_count(val) + 1) self._stack.append(val) def peek(self, count=None): if count is None: return self._stack[-1] else: return self._stack[-count:] def code_walker(code): l = len(code) code = array('B', code) i = 0 extended_arg = 0 while i < l: op = code[i] if op >= HAVE_ARGUMENT: oparg = code[i + 1] + code[i + 2] * 256 + extended_arg extended_arg = 0 if op == EXTENDED_ARG: extended_arg = oparg * 65536 yield i, (op, oparg) i += 3 else: yield i, (op, None) i += 1 class Code: def __init__(self, code_obj, parent=None): self.code_obj = code_obj self.parent = parent self.derefnames = [PyName(v) for v in code_obj.co_cellvars + code_obj.co_freevars] self.consts = list(map(PyConst, code_obj.co_consts)) self.names = list(map(PyName, code_obj.co_names)) self.varnames = list(map(PyName, code_obj.co_varnames)) self.instr_seq = list(code_walker(code_obj.co_code)) self.instr_map = {addr: i for i, (addr, _) in enumerate(self.instr_seq)} self.name = code_obj.co_name self.globals = [] self.nonlocals = [] self.find_else() def __getitem__(self, instr_index): if 0 <= instr_index < len(self.instr_seq): return Address(self, instr_index) def __iter__(self): for i in range(len(self.instr_seq)): yield Address(self, i) def show(self): for addr in self: print(addr) def address(self, addr): return self[self.instr_map[addr]] def iscellvar(self, i): return i < len(self.code_obj.co_cellvars) def find_else(self): jumps = {} last_jump = None for addr in self: opcode, arg = addr if opcode in pop_jump_if_opcodes: jump_addr = self.address(arg) if (jump_addr[-1].opcode in else_jump_opcodes or jump_addr.opcode == FOR_ITER): last_jump = addr jumps[jump_addr] = addr elif opcode == JUMP_ABSOLUTE: # This case is to deal with some nested ifs such as: # if a: # if b: # f() # elif c: # g() jump_addr = self.address(arg) if jump_addr in jumps: jumps[addr] = jumps[jump_addr] elif opcode in stmt_opcodes and last_jump is not None: # This opcode will generate a statement, so it means # that the last POP_JUMP_IF_x was an else-jump jumps[addr] = last_jump self.else_jumps = set(jumps.values()) def get_suite(self, include_declarations=True, look_for_docstring=False): dec = SuiteDecompiler(self[0]) dec.run() first_stmt = dec.suite and dec.suite[0] # Change __doc__ = "docstring" to "docstring" if look_for_docstring and isinstance(first_stmt, AssignStatement): chain = first_stmt.chain if len(chain) == 2 and str(chain[0]) == "__doc__": dec.suite[0] = DocString(first_stmt.chain[1].val) if include_declarations and (self.globals or self.nonlocals): suite = Suite() if self.globals: stmt = "global " + ", ".join(map(str, self.globals)) suite.add_statement(SimpleStatement(stmt)) if self.nonlocals: stmt = "nonlocal " + ", ".join(map(str, self.nonlocals)) suite.add_statement(SimpleStatement(stmt)) for stmt in dec.suite: suite.add_statement(stmt) return suite else: return dec.suite def declare_global(self, name): """ Declare name as a global. Called by STORE_GLOBAL and DELETE_GLOBAL """ if name not in self.globals: self.globals.append(name) def ensure_global(self, name): """ Declare name as global only if it is also a local variable name in one of the surrounding code objects. This is called by LOAD_GLOBAL """ parent = self.parent while parent: if name in parent.varnames: return self.declare_global(name) parent = parent.parent def declare_nonlocal(self, name): """ Declare name as nonlocal. Called by STORE_DEREF and DELETE_DEREF (but only when the name denotes a free variable, not a cell one). """ if name not in self.nonlocals: self.nonlocals.append(name) class Address: def __init__(self, code, instr_index): self.code = code self.index = instr_index self.addr, (self.opcode, self.arg) = code.instr_seq[instr_index] def __eq__(self, other): return (isinstance(other, type(self)) and self.code == other.code and self.index == other.index) def __lt__(self, other): return other is None or (isinstance(other, type(self)) and self.code == other.code and self.index < other.index) def __str__(self): mark = "*" if self in self.code.else_jumps else " " return "{} {} {} {}".format( mark, self.addr, opname[self.opcode], self.arg or "" ) def __add__(self, delta): return self.code.address(self.addr + delta) def __getitem__(self, index): return self.code[self.index + index] def __iter__(self): yield self.opcode yield self.arg def __hash__(self): return hash((self.code, self.index)) def is_else_jump(self): return self in self.code.else_jumps def change_instr(self, opcode, arg=None): self.code.instr_seq[self.index] = (self.addr, (opcode, arg)) def jump(self): opcode = self.opcode if opcode in dis.hasjrel: return self[1] + self.arg elif opcode in dis.hasjabs: return self.code.address(self.arg) class PyExpr: def wrap(self, condition=True): if condition: return "({})".format(self) else: return str(self) def store(self, dec, dest): chain = dec.assignment_chain chain.append(dest) if self not in dec.stack: chain.append(self) dec.suite.add_statement(AssignStatement(chain)) dec.assignment_chain = [] def on_pop(self, dec): dec.write(str(self)) class PyConst(PyExpr): precedence = 100 def __init__(self, val): self.val = val def __str__(self): return repr(self.val) def __iter__(self): return iter(self.val) def __eq__(self, other): return isinstance(other, PyConst) and self.val == other.val class PyTuple(PyExpr): precedence = 0 def __init__(self, values): self.values = values def __str__(self): if not self.values: return "()" valstr = [val.wrap(val.precedence <= self.precedence) for val in self.values] if len(valstr) == 1: return '(' + valstr[0] + "," + ')' else: return '(' + ", ".join(valstr) + ')' def __iter__(self): return iter(self.values) def wrap(self, condition=True): return str(self) class PyList(PyExpr): precedence = 16 def __init__(self, values): self.values = values def __str__(self): valstr = ", ".join(val.wrap(val.precedence <= 0) for val in self.values) return "[{}]".format(valstr) def __iter__(self): return iter(self.values) class PySet(PyExpr): precedence = 16 def __init__(self, values): self.values = values def __str__(self): valstr = ", ".join(val.wrap(val.precedence <= 0) for val in self.values) return "{{{}}}".format(valstr) def __iter__(self): return iter(self.values) class PyDict(PyExpr): precedence = 16 def __init__(self): self.items = [] def set_item(self, key, val): self.items.append((key, val)) def __str__(self): itemstr = ", ".join("{}: {}".format(*kv) for kv in self.items) return "{{{}}}".format(itemstr) class PyName(PyExpr): precedence = 100 def __init__(self, name): self.name = name def __str__(self): return self.name def __eq__(self, other): return isinstance(other, type(self)) and self.name == other.name class PyUnaryOp(PyExpr): def __init__(self, operand): self.operand = operand def __str__(self): opstr = self.operand.wrap(self.operand.precedence < self.precedence) return self.pattern.format(opstr) @classmethod def instr(cls, stack): stack.push(cls(stack.pop())) class PyBinaryOp(PyExpr): def __init__(self, left, right): self.left = left self.right = right def wrap_left(self): return self.left.wrap(self.left.precedence < self.precedence) def wrap_right(self): return self.right.wrap(self.right.precedence <= self.precedence) def __str__(self): return self.pattern.format(self.wrap_left(), self.wrap_right()) @classmethod def instr(cls, stack): right = stack.pop() left = stack.pop() stack.push(cls(left, right)) class PySubscript(PyBinaryOp): precedence = 15 pattern = "{}[{}]" def wrap_right(self): return str(self.right) class PySlice(PyExpr): precedence = 1 def __init__(self, args): assert len(args) in (2, 3) if len(args) == 2: self.start, self.stop = args self.step = None else: self.start, self.stop, self.step = args if self.start == PyConst(None): self.start = "" if self.stop == PyConst(None): self.stop = "" def __str__(self): if self.step is None: return "{}:{}".format(self.start, self.stop) else: return "{}:{}:{}".format(self.start, self.stop, self.step) class PyCompare(PyExpr): precedence = 6 def __init__(self, complist): self.complist = complist def __str__(self): return " ".join(x if i % 2 else x.wrap(x.precedence <= 0) for i, x in enumerate(self.complist)) def extends(self, other): if not isinstance(other, PyCompare): return False else: return self.complist[0] == other.complist[-1] def chain(self, other): return PyCompare(self.complist + other.complist[1:]) class PyBooleanAnd(PyBinaryOp): precedence = 4 pattern = "{} and {}" class PyBooleanOr(PyBinaryOp): precedence = 3 pattern = "{} or {}" class PyIfElse(PyExpr): precedence = 2 def __init__(self, cond, true_expr, false_expr): self.cond = cond self.true_expr = true_expr self.false_expr = false_expr def __str__(self): p = self.precedence cond_str = self.cond.wrap(self.cond.precedence <= p) true_str = self.true_expr.wrap(self.cond.precedence <= p) false_str = self.false_expr.wrap(self.cond.precedence < p) return "{} if {} else {}".format(true_str, cond_str, false_str) class PyAttribute(PyExpr): precedence = 15 def __init__(self, expr, attrname): self.expr = expr self.attrname = attrname def __str__(self): expr_str = self.expr.wrap(self.expr.precedence < self.precedence) return "{}.{}".format(expr_str, self.attrname) class PyCallFunction(PyExpr): precedence = 15 def __init__(self, func, args, kwargs, varargs=None, varkw=None): self.func = func self.args = args self.kwargs = kwargs self.varargs = varargs self.varkw = varkw def __str__(self): funcstr = self.func.wrap(self.func.precedence < self.precedence) if len(self.args) == 1 and not (self.kwargs or self.varargs or self.varkw): arg = self.args[0] if isinstance(arg, PyGenExpr): # Only one pair of brackets arount a single arg genexpr return "{}{}".format(funcstr, arg) args = [x.wrap(x.precedence <= 0) for x in self.args] args.extend("{}={}".format(k.val, v.wrap(v.precedence <= 0)) for k, v in self.kwargs) if self.varargs is not None: args.append("*{}".format(self.varargs)) if self.varkw is not None: args.append("**{}".format(self.varkw)) return "{}({})".format(funcstr, ", ".join(args)) class FunctionDefinition: def __init__(self, code, defaults, kwdefaults, closure, paramobjs={}): self.code = code self.defaults = defaults self.kwdefaults = kwdefaults self.closure = closure self.paramobjs = paramobjs def getparams(self): code_obj = self.code.code_obj l = code_obj.co_argcount params = list(code_obj.co_varnames[:l]) if self.defaults: for i, arg in enumerate(reversed(self.defaults)): name = params[-i - 1] if name in self.paramobjs: params[-i - 1] = "{}:{}={}".format(name, self.paramobjs[name], arg) else: params[-i - 1] = "{}={}".format(name, arg) kwcount = code_obj.co_kwonlyargcount kwparams = [] if kwcount: for i in range(kwcount): name = code_obj.co_varnames[l + i] if name in self.kwdefaults and name in self.paramobjs: kwparams.append("{}:{}={}".format(name, self.paramobjs[name], self.kwdefaults[name])) elif name in self.kwdefaults: kwparams.append("{}={}".format(name, self.kwdefaults[name])) else: kwparams.append(name) l += kwcount if code_obj.co_flags & VARARGS: params.append("*" + code_obj.co_varnames[l]) l += 1 elif kwparams: params.append("*") params.extend(kwparams) if code_obj.co_flags & VARKEYWORDS: params.append("**" + code_obj.co_varnames[l]) return params def getreturn(self): if self.paramobjs and 'return' in self.paramobjs: return self.paramobjs['return'] return None class PyLambda(PyExpr, FunctionDefinition): precedence = 1 def __str__(self): suite = self.code.get_suite() params = ", ".join(self.getparams()) if len(suite.statements) > 0: def strip_return(val): return val[len("return "):] if val.startswith('return') else val if isinstance(suite[0], IfStatement) and len(suite.statements) == 2: expr = "return {} if {} else {}".format( strip_return(str(suite[0].true_suite)), str(suite[0].cond), strip_return(str(suite[1])) ) else: expr = strip_return(str(suite[0])) else: expr = "None" return "lambda {}: {}".format(params, expr) class PyComp(PyExpr): """ Abstraction for list, set, dict comprehensions and generator expressions """ precedence = 16 def __init__(self, code, defaults, kwdefaults, closure, paramobjs={}): assert not defaults and not kwdefaults self.code = code code[0].change_instr(NOP) last_i = len(code.instr_seq) - 1 code[last_i].change_instr(NOP) def set_iterable(self, iterable): self.code.varnames[0] = iterable def __str__(self): suite = self.code.get_suite() return self.pattern.format(suite.gen_display()) class PyListComp(PyComp): pattern = "[{}]" class PySetComp(PyComp): pattern = "{{{}}}" class PyKeyValue(PyBinaryOp): """This is only to create dict comprehensions""" precedence = 1 pattern = "{}: {}" class PyDictComp(PyComp): pattern = "{{{}}}" class PyGenExpr(PyComp): precedence = 16 pattern = "({})" def __init__(self, code, defaults, kwdefaults, closure, paramobjs={}): self.code = code class PyYield(PyExpr): precedence = 1 def __init__(self, value): self.value = value def __str__(self): return "yield {}".format(self.value) class PyYieldFrom(PyExpr): precedence = 1 def __init__(self, value): self.value = value def __str__(self): return "yield from {}".format(self.value) class PyStarred(PyExpr): """Used in unpacking assigments""" precedence = 15 def __init__(self, expr): self.expr = expr def __str__(self): es = self.expr.wrap(self.expr.precedence < self.precedence) return "*{}".format(es) code_map = { '': PyLambda, '': PyListComp, '': PySetComp, '': PyDictComp, '': PyGenExpr, } unary_ops = [ ('UNARY_POSITIVE', 'Positive', '+{}', 13), ('UNARY_NEGATIVE', 'Negative', '-{}', 13), ('UNARY_NOT', 'Not', 'not {}', 5), ('UNARY_INVERT', 'Invert', '~{}', 13), ] binary_ops = [ ('POWER', 'Power', '{}**{}', 14, '{} **= {}'), ('MULTIPLY', 'Multiply', '{}*{}', 12, '{} *= {}'), ('FLOOR_DIVIDE', 'FloorDivide', '{}//{}', 12, '{} //= {}'), ('TRUE_DIVIDE', 'TrueDivide', '{}/{}', 12, '{} /= {}'), ('MODULO', 'Modulo', '{} % {}', 12, '{} %= {}'), ('ADD', 'Add', '{} + {}', 11, '{} += {}'), ('SUBTRACT', 'Subtract', '{} - {}', 11, '{} -= {}'), ('SUBSCR', 'Subscript', '{}[{}]', 15, None), ('LSHIFT', 'LeftShift', '{} << {}', 10, '{} <<= {}'), ('RSHIFT', 'RightShift', '{} >> {}', 10, '{} >>= {}'), ('AND', 'And', '{} & {}', 9, '{} &= {}'), ('XOR', 'Xor', '{} ^ {}', 8, '{} ^= {}'), ('OR', 'Or', '{} | {}', 7, '{} |= {}'), ] class PyStatement: def __str__(self): istr = IndentString() self.display(istr) return str(istr) def wrap(self, condition=True): if condition: assert not condition return "({})".format(self) else: return str(self) def on_pop(self, dec): # dec.write("#ERROR: Unexpected context 'on_pop': pop on statement: ") pass class DocString(PyStatement): def __init__(self, string): self.string = string def display(self, indent): if '\n' not in self.string: indent.write(repr(self.string)) else: if "'''" not in self.string: fence = "'''" elif '"""' not in self.string: fence = '"""' else: raise NotImplemented lines = self.string.split('\n') text = '\n'.join(l.encode('unicode_escape').decode() for l in lines) docstring = "{0}{1}{0}".format(fence, text) indent.write(docstring) class AssignStatement(PyStatement): def __init__(self, chain): self.chain = chain def display(self, indent): indent.write(" = ".join(map(str, self.chain))) class InPlaceOp(PyStatement): def __init__(self, left, right): self.right = right self.left = left def store(self, dec, dest): # assert dest is self.left dec.suite.add_statement(self) def display(self, indent): indent.write(self.pattern, self.left, self.right) class Unpack: precedence = 50 def __init__(self, val, length, star_index=None): self.val = val self.length = length self.star_index = star_index self.dests = [] def store(self, dec, dest): if len(self.dests) == self.star_index: dest = PyStarred(dest) self.dests.append(dest) if len(self.dests) == self.length: dec.stack.push(self.val) dec.store(PyTuple(self.dests)) class ImportStatement(PyStatement): alias = "" precedence = 100 def __init__(self, name, level, fromlist): self.name = name self.alias = name self.level = level self.fromlist = fromlist self.aslist = [] def store(self, dec, dest): self.alias = dest dec.suite.add_statement(self) def on_pop(self, dec): dec.suite.add_statement(self) def display(self, indent): if self.fromlist == PyConst(None): name = self.name.name alias = self.alias.name if name == alias or name.startswith(alias + "."): indent.write("import {}", name) else: indent.write("import {} as {}", name, alias) elif self.fromlist == PyConst(('*',)): indent.write("from {} import *", self.name.name) else: names = [] for name, alias in zip(self.fromlist, self.aslist): if name == alias: names.append(name) else: names.append("{} as {}".format(name, alias)) indent.write("from {} import {}", self.name, ", ".join(names)) class ImportFrom: def __init__(self, name): self.name = name def store(self, dec, dest): imp = dec.stack.peek() assert isinstance(imp, ImportStatement) imp.aslist.append(dest.name) class SimpleStatement(PyStatement): def __init__(self, val): assert val is not None self.val = val def display(self, indent): indent.write(self.val) def gen_display(self, seq=()): return " ".join((self.val,) + seq) class IfStatement(PyStatement): def __init__(self, cond, true_suite, false_suite): self.cond = cond self.true_suite = true_suite self.false_suite = false_suite def display(self, indent, is_elif=False): ptn = "elif {}:" if is_elif else "if {}:" indent.write(ptn, self.cond) self.true_suite.display(indent + 1) if not self.false_suite: return if len(self.false_suite) == 1: stmt = self.false_suite[0] if isinstance(stmt, IfStatement): stmt.display(indent, is_elif=True) return indent.write("else:") self.false_suite.display(indent + 1) def gen_display(self, seq=()): assert not self.false_suite s = "if {}".format(self.cond) return self.true_suite.gen_display(seq + (s,)) class ForStatement(PyStatement): def __init__(self, iterable): self.iterable = iterable def store(self, dec, dest): self.dest = dest def display(self, indent): indent.write("for {} in {}:", self.dest, self.iterable) self.body.display(indent + 1) def gen_display(self, seq=()): s = "for {} in {}".format(self.dest, self.iterable) return self.body.gen_display(seq + (s,)) class WhileStatement(PyStatement): def __init__(self, cond, body): self.cond = cond self.body = body def display(self, indent): indent.write("while {}:", self.cond) self.body.display(indent + 1) class DecorableStatement(PyStatement): def __init__(self): self.decorators = [] def display(self, indent): indent.sep() for f in reversed(self.decorators): indent.write("@{}", f) self.display_undecorated(indent) indent.sep() def decorate(self, f): self.decorators.append(f) class DefStatement(FunctionDefinition, DecorableStatement): def __init__(self, code, defaults, kwdefaults, closure, paramobjs={}): FunctionDefinition.__init__(self, code, defaults, kwdefaults, closure, paramobjs) DecorableStatement.__init__(self) def display_undecorated(self, indent): paramlist = ", ".join(self.getparams()) result = self.getreturn() if result: indent.write("def {}({}) -> {}:", self.code.name, paramlist, result) else: indent.write("def {}({}):", self.code.name, paramlist) # Assume that co_consts starts with None unless the function # has a docstring, in which case it starts with the docstring if self.code.consts[0] != PyConst(None): docstring = self.code.consts[0].val DocString(docstring).display(indent + 1) self.code.get_suite().display(indent + 1) def store(self, dec, dest): self.name = dest dec.suite.add_statement(self) class TryStatement(PyStatement): def __init__(self, try_suite): self.try_suite = try_suite self.except_clauses = [] def add_except_clause(self, type, suite): self.except_clauses.append([type, None, suite]) def store(self, dec, dest): self.except_clauses[-1][1] = dest def display(self, indent): indent.write("try:") self.try_suite.display(indent + 1) for type, name, suite in self.except_clauses: if type is None: indent.write("except:") elif name is None: indent.write("except {}:", type) else: indent.write("except {} as {}:", type, name) suite.display(indent + 1) class FinallyStatement(PyStatement): def __init__(self, try_suite, finally_suite): self.try_suite = try_suite self.finally_suite = finally_suite def display(self, indent): # Wrap the try suite in a TryStatement if necessary try_stmt = None if len(self.try_suite) == 1: try_stmt = self.try_suite[0] if not isinstance(try_stmt, TryStatement): try_stmt = None if try_stmt is None: try_stmt = TryStatement(self.try_suite) try_stmt.display(indent) indent.write("finally:") self.finally_suite.display(indent + 1) class WithStatement(PyStatement): def __init__(self, with_expr): self.with_expr = with_expr self.with_name = None def store(self, dec, dest): self.with_name = dest def display(self, indent, args=None): # args to take care of nested withs: # with x as t: # with y as u: # # ---> # with x as t, y as u: # if args is None: args = [] if self.with_name is None: args.append(str(self.with_expr)) else: args.append("{} as {}".format(self.with_expr, self.with_name)) if len(self.suite) == 1 and isinstance(self.suite[0], WithStatement): self.suite[0].display(indent, args) else: indent.write("with {}:", ", ".join(args)) self.suite.display(indent + 1) class ClassStatement(DecorableStatement): def __init__(self, func, name, parents, kwargs): DecorableStatement.__init__(self) self.func = func self.parents = parents self.kwargs = kwargs def store(self, dec, dest): self.name = dest dec.suite.add_statement(self) def display_undecorated(self, indent): if self.parents or self.kwargs: args = [str(x) for x in self.parents] kwargs = ["{}={}".format(k.val, v) for k, v in self.kwargs] all_args = ", ".join(args + kwargs) indent.write("class {}({}):", self.name, all_args) else: indent.write("class {}:", self.name) suite = self.func.code.get_suite(look_for_docstring=True) if suite: # TODO: find out why sometimes the class suite ends with # "return __class__" last_stmt = suite[-1] if isinstance(last_stmt, SimpleStatement): if last_stmt.val.startswith("return "): suite.statements.pop() suite.display(indent + 1) class Suite: def __init__(self): self.statements = [] def __bool__(self): return bool(self.statements) def __len__(self): return len(self.statements) def __getitem__(self, i): return self.statements[i] def __setitem__(self, i, val): self.statements[i] = val def __str__(self): istr = IndentString() self.display(istr) return str(istr) def display(self, indent): if self.statements: for stmt in self.statements: stmt.display(indent) else: indent.write("pass") def gen_display(self, seq=()): assert len(self) == 1 return self[0].gen_display(seq) def add_statement(self, stmt): self.statements.append(stmt) class SuiteDecompiler: # An instruction handler can return this to indicate to the run() # function that it should return immediately END_NOW = object() # This is put on the stack by LOAD_BUILD_CLASS BUILD_CLASS = object() def __init__(self, start_addr, end_addr=None, stack=None): self.start_addr = start_addr self.end_addr = end_addr self.code = start_addr.code self.stack = Stack() if stack is None else stack self.suite = Suite() self.assignment_chain = [] self.popjump_stack = [] def push_popjump(self, jtruthiness, jaddr, jcond): stack = self.popjump_stack if jaddr and jaddr[-1].is_else_jump(): # Increase jaddr to the 'else' address if it jumps to the 'then' jaddr = jaddr[-1].jump() while stack: truthiness, addr, cond = stack[-1] if jaddr == None: print("#ERROR: jaddr is None") if jaddr == None or jaddr < addr or jaddr == addr: break stack.pop() obj_maker = PyBooleanOr if truthiness else PyBooleanAnd if isinstance(jcond, obj_maker): # Use associativity of 'and' and 'or' to minimise the # number of parentheses jcond = obj_maker(obj_maker(cond, jcond.left), jcond.right) else: jcond = obj_maker(cond, jcond) stack.append((jtruthiness, jaddr, jcond)) def pop_popjump(self): truthiness, addr, cond = self.popjump_stack.pop() return cond def run(self): addr, end_addr = self.start_addr, self.end_addr while addr and addr < end_addr: opcode, arg = addr method = getattr(self, opname[opcode]) if arg is None: new_addr = method(addr) else: new_addr = method(addr, arg) if new_addr is self.END_NOW: break elif new_addr is None: new_addr = addr[1] addr = new_addr return addr def write(self, template, *args): def fmt(x): if isinstance(x, int): return self.stack.getval(x) else: return x if args: line = template.format(*map(fmt, args)) else: line = template self.suite.add_statement(SimpleStatement(line)) def store(self, dest): val = self.stack.pop() val.store(self, dest) def scan_to_first_jump_if(self, addr, end_addr): i = 0 while 1: cur_addr = addr[i] if cur_addr == end_addr: break elif cur_addr.opcode in pop_jump_if_opcodes: return cur_addr elif cur_addr.opcode in else_jump_opcodes: break elif cur_addr.opcode in for_jump_opcodes: break i = i + 1 return None def scan_for_final_jump(self, start_addr, end_addr): i = 0 while 1: cur_addr = end_addr[i] if cur_addr == start_addr: break elif cur_addr.opcode == JUMP_ABSOLUTE: return cur_addr elif cur_addr.opcode in else_jump_opcodes: break elif cur_addr.opcode in pop_jump_if_opcodes: break i = i - 1 return None # # All opcode methods in CAPS below. # def SETUP_LOOP(self, addr, delta): jump_addr = addr[1] + delta end_addr = jump_addr[-1] if end_addr.opcode == JUMP_ABSOLUTE: # while 1 ??? d_body = SuiteDecompiler(addr[1], end_addr) while_stmt = WhileStatement(PyConst(True), d_body.suite) d_body.stack.push(while_stmt) d_body.run() while_stmt.body = d_body.suite self.suite.add_statement(while_stmt) return jump_addr elif end_addr.opcode == POP_BLOCK: # assume conditional # scan to first jump end_cond = self.scan_to_first_jump_if(addr[1], end_addr) if end_cond: # scan for conditional d_cond = SuiteDecompiler(addr[1], end_cond) # d_cond.run() cond = d_cond.stack.pop() if end_cond.opcode == POP_JUMP_IF_TRUE: cond = PyNot(cond) d_body = SuiteDecompiler(end_cond[1], end_addr) while_stmt = WhileStatement(cond, d_body.suite) d_body.stack.push(while_stmt) d_body.run() while_stmt.body = d_body.suite self.suite.add_statement(while_stmt) return jump_addr return None def BREAK_LOOP(self, addr): self.write("break") def CONTINUE_LOOP(self, addr, *argv): self.write("continue") def SETUP_FINALLY(self, addr, delta): start_finally = addr.jump() d_try = SuiteDecompiler(addr[1], start_finally) d_try.run() d_finally = SuiteDecompiler(start_finally) end_finally = d_finally.run() self.suite.add_statement(FinallyStatement(d_try.suite, d_finally.suite)) return end_finally[1] def END_FINALLY(self, addr): return self.END_NOW def SETUP_EXCEPT(self, addr, delta): start_except = addr.jump() end_try = start_except[-1] d_try = SuiteDecompiler(addr[1], start_except[-1]) d_try.run() if end_try.opcode == JUMP_FORWARD: end_addr = end_try[1] + end_try.arg elif end_try.opcode == JUMP_ABSOLUTE: end_addr = end_try.arg else: # print(repr(end_try.opcode)) assert end_try.opcode == JUMP_FORWARD stmt = TryStatement(d_try.suite) while start_except.opcode != END_FINALLY: if start_except.opcode == DUP_TOP: # There's a new except clause d_except = SuiteDecompiler(start_except[1]) d_except.stack.push(stmt) d_except.run() start_except = stmt.next_start_except elif start_except.opcode == POP_TOP: # It's a bare except clause - it starts: # POP_TOP # POP_TOP # POP_TOP # # POP_EXCEPT d_except = SuiteDecompiler(start_except[3]) end_except = d_except.run() stmt.add_except_clause(None, d_except.suite) start_except = end_except[2] assert start_except.opcode == END_FINALLY self.suite.add_statement(stmt) return start_except[1] def SETUP_WITH(self, addr, delta): end_with = addr.jump() with_stmt = WithStatement(self.stack.pop()) d_with = SuiteDecompiler(addr[1], end_with) d_with.stack.push(with_stmt) d_with.run() with_stmt.suite = d_with.suite self.suite.add_statement(with_stmt) assert end_with.opcode == WITH_CLEANUP assert end_with[1].opcode == END_FINALLY return end_with[2] def POP_BLOCK(self, addr): # print("** POP BLOCK:", addr) pass def POP_EXCEPT(self, addr): # print("** POP EXCEPT:", addr) return self.END_NOW def NOP(self, addr): return def COMPARE_OP(self, addr, opname): left, right = self.stack.pop(2) if opname != 10: # 10 is exception match self.stack.push(PyCompare([left, cmp_op[opname], right])) else: # It's an exception match # left is a TryStatement # right is the exception type to be matched # It goes: # COMPARE_OP 10 # POP_JUMP_IF_FALSE # POP_TOP # POP_TOP or STORE_FAST (if the match is named) # POP_TOP # SETUP_FINALLY if the match was named assert addr[1].opcode == POP_JUMP_IF_FALSE left.next_start_except = addr[1].jump() assert addr[2].opcode == POP_TOP assert addr[4].opcode == POP_TOP if addr[5].opcode == SETUP_FINALLY: except_start = addr[6] except_end = addr[5].jump() else: except_start = addr[5] except_end = left.next_start_except[-1] d_body = SuiteDecompiler(except_start, except_end) d_body.run() left.add_except_clause(right, d_body.suite) if addr[3].opcode != POP_TOP: # The exception is named d_exc_name = SuiteDecompiler(addr[3], addr[4]) d_exc_name.stack.push(left) # This will store the name in left: d_exc_name.run() # We're done with this except clause return self.END_NOW # # Stack manipulation # def POP_TOP(self, addr): self.stack.pop().on_pop(self) def ROT_TWO(self, addr): tos1, tos = self.stack.pop(2) self.stack.push(tos, tos1) def ROT_THREE(self, addr): tos2, tos1, tos = self.stack.pop(3) self.stack.push(tos, tos2, tos1) def DUP_TOP(self, addr): self.stack.push(self.stack.peek()) def DUP_TOP_TWO(self, addr): self.stack.push(*self.stack.peek(2)) # # LOAD / STORE / DELETE # # FAST def LOAD_FAST(self, addr, var_num): name = self.code.varnames[var_num] self.stack.push(name) def STORE_FAST(self, addr, var_num): name = self.code.varnames[var_num] self.store(name) def DELETE_FAST(self, addr, var_num): name = self.code.varnames[var_num] self.write("del {}", name) # DEREF def LOAD_DEREF(self, addr, i): name = self.code.derefnames[i] self.stack.push(name) def STORE_DEREF(self, addr, i): name = self.code.derefnames[i] if not self.code.iscellvar(i): self.code.declare_nonlocal(name) self.store(name) def DELETE_DEREF(self, addr, i): name = self.code.derefnames[i] if not self.code.iscellvar(i): self.code.declare_nonlocal(name) self.write("del {}", name) # GLOBAL def LOAD_GLOBAL(self, addr, namei): name = self.code.names[namei] self.code.ensure_global(name) self.stack.push(name) def STORE_GLOBAL(self, addr, namei): name = self.code.names[namei] self.code.declare_global(name) self.store(name) def DELETE_GLOBAL(self, addr, namei): name = self.code.names[namei] self.declare_global(name) self.write("del {}", name) # NAME def LOAD_NAME(self, addr, namei): name = self.code.names[namei] self.stack.push(name) def STORE_NAME(self, addr, namei): name = self.code.names[namei] self.store(name) def DELETE_NAME(self, addr, namei): name = self.code.names[namei] self.write("del {}", name) # ATTR def LOAD_ATTR(self, addr, namei): expr = self.stack.pop() attrname = self.code.names[namei] self.stack.push(PyAttribute(expr, attrname)) def STORE_ATTR(self, addr, namei): expr = self.stack.pop() attrname = self.code.names[namei] self.store(PyAttribute(expr, attrname)) def DELETE_ATTR(self, addr, namei): expr = self.stack.pop() attrname = self.code.names[namei] self.write("del {}.{}", expr, attrname) # SUBSCR def STORE_SUBSCR(self, addr): expr, sub = self.stack.pop(2) self.store(PySubscript(expr, sub)) def DELETE_SUBSCR(self, addr): expr, sub = self.stack.pop(2) self.write("del {}[{}]", expr, sub) # CONST def LOAD_CONST(self, addr, consti): const = self.code.consts[consti] self.stack.push(const) # # Import statements # def IMPORT_NAME(self, addr, namei): name = self.code.names[namei] level, fromlist = self.stack.pop(2) self.stack.push(ImportStatement(name, level, fromlist)) # special case check for import x.y.z as w syntax which uses # attributes and assignments and is difficult to workaround i = 1 while addr[i].opcode == LOAD_ATTR: i = i + 1 if i > 1 and addr[i].opcode in (STORE_FAST, STORE_NAME): return addr[i] return None def IMPORT_FROM(self, addr, namei): name = self.code.names[namei] self.stack.push(ImportFrom(name)) def IMPORT_STAR(self, addr): self.POP_TOP(addr) # # Function call # def STORE_LOCALS(self, addr): self.stack.pop() return addr[3] def LOAD_BUILD_CLASS(self, addr): self.stack.push(self.BUILD_CLASS) def RETURN_VALUE(self, addr): value = self.stack.pop() if isinstance(value, PyConst) and value.val is None: if addr[1] is not None: self.write("return") return self.write("return {}", value) def YIELD_VALUE(self, addr): if self.code.name == '': return value = self.stack.pop() self.stack.push(PyYield(value)) def YIELD_FROM(self, addr): value = self.stack.pop() # TODO: from statement ? value = self.stack.pop() self.stack.push(PyYield(value)) def CALL_FUNCTION(self, addr, argc, have_var=False, have_kw=False): kw_argc = argc >> 8 pos_argc = argc & 0xFF varkw = self.stack.pop() if have_kw else None varargs = self.stack.pop() if have_var else None kwargs_iter = iter(self.stack.pop(2 * kw_argc)) kwargs = list(zip(kwargs_iter, kwargs_iter)) posargs = self.stack.pop(pos_argc) func = self.stack.pop() if func is self.BUILD_CLASS: # It's a class construction # TODO: check the assert statement below is correct assert not (have_var or have_kw) func, name, *parents = posargs self.stack.push(ClassStatement(func, name, parents, kwargs)) elif isinstance(func, PyComp): # It's a list/set/dict comprehension or generator expression assert not (have_var or have_kw) assert len(posargs) == 1 and not kwargs func.set_iterable(posargs[0]) self.stack.push(func) elif posargs and isinstance(posargs[0], DecorableStatement): # It's a decorator for a def/class statement assert len(posargs) == 1 and not kwargs defn = posargs[0] defn.decorate(func) self.stack.push(defn) else: # It's none of the above, so it must be a normal function call func_call = PyCallFunction(func, posargs, kwargs, varargs, varkw) self.stack.push(func_call) def CALL_FUNCTION_VAR(self, addr, argc): self.CALL_FUNCTION(addr, argc, have_var=True) def CALL_FUNCTION_KW(self, addr, argc): self.CALL_FUNCTION(addr, argc, have_kw=True) def CALL_FUNCTION_VAR_KW(self, addr, argc): self.CALL_FUNCTION(addr, argc, have_var=True, have_kw=True) # a, b, ... = ... def UNPACK_SEQUENCE(self, addr, count): unpack = Unpack(self.stack.pop(), count) for i in range(count): self.stack.push(unpack) def UNPACK_EX(self, addr, counts): rcount = counts >> 8 lcount = counts & 0xFF count = lcount + rcount + 1 unpack = Unpack(self.stack.pop(), count, lcount) for i in range(count): self.stack.push(unpack) # special case: x, y = z, t def ROT_TWO(self, addr): val = PyTuple(self.stack.pop(2)) unpack = Unpack(val, 2) self.stack.push(unpack) self.stack.push(unpack) # Build operations def BUILD_SLICE(self, addr, argc): assert argc in (2, 3) self.stack.push(PySlice(self.stack.pop(argc))) def BUILD_TUPLE(self, addr, count): values = [self.stack.pop() for i in range(count)] values.reverse() self.stack.push(PyTuple(values)) def BUILD_LIST(self, addr, count): values = [self.stack.pop() for i in range(count)] values.reverse() self.stack.push(PyList(values)) def BUILD_SET(self, addr, count): values = [self.stack.pop() for i in range(count)] values.reverse() self.stack.push(PySet(values)) def BUILD_MAP(self, addr, count): self.stack.push(PyDict()) def STORE_MAP(self, addr): v, k = self.stack.pop(2) d = self.stack.peek() d.set_item(k, v) # Comprehension operations - just create an expression statement def LIST_APPEND(self, addr, i): self.POP_TOP(addr) def SET_ADD(self, addr, i): self.POP_TOP(addr) def MAP_ADD(self, addr, i): value, key = self.stack.pop(2) self.stack.push(PyKeyValue(key, value)) self.POP_TOP(addr) # and operator def JUMP_IF_FALSE_OR_POP(self, addr, target): end_addr = addr.jump() self.push_popjump(True, end_addr, self.stack.pop()) left = self.pop_popjump() if end_addr.opcode == ROT_TWO: opc, arg = end_addr[-1] if opc == JUMP_FORWARD and arg == 2: end_addr = end_addr[2] d = SuiteDecompiler(addr[1], end_addr, self.stack) d.run() right = self.stack.pop() if isinstance(right, PyCompare) and right.extends(left): py_and = left.chain(right) else: py_and = PyBooleanAnd(left, right) self.stack.push(py_and) return end_addr # This appears when there are chained comparisons, e.g. 1 <= x < 10 def JUMP_FORWARD(self, addr, delta): # print("*** JUMP FORWARD", addr) ## if delta == 2 and addr[1].opcode == ROT_TWO and addr[2].opcode == POP_TOP: ## # We're in the special case of chained comparisons ## return addr[3] ## else: ## # I'm hoping its an unused JUMP in an if-else statement ## return addr[1] return addr.jump() # or operator def JUMP_IF_TRUE_OR_POP(self, addr, target): end_addr = addr.jump() self.push_popjump(True, end_addr, self.stack.pop()) left = self.pop_popjump() d = SuiteDecompiler(addr[1], end_addr, self.stack) d.run() right = self.stack.pop() self.stack.push(PyBooleanOr(left, right)) return end_addr # # If-else statements/expressions and related structures # def POP_JUMP_IF(self, addr, target, truthiness): jump_addr = addr.jump() if jump_addr.opcode == FOR_ITER: # We are in a for-loop with nothing after the if-suite # But take care: for-loops in generator expression do # not end in POP_BLOCK, hence the test below. jump_addr = jump_addr.jump() elif jump_addr[-1].opcode == SETUP_LOOP: # We are in a while-loop with nothing after the if-suite jump_addr = jump_addr[-1].jump()[-1] cond = self.stack.pop() if not addr.is_else_jump(): self.push_popjump(truthiness, jump_addr, cond) return # Increase jump_addr to pop all previous jumps self.push_popjump(truthiness, jump_addr[1], cond) cond = self.pop_popjump() end_true = jump_addr[-1] if truthiness: cond = PyNot(cond) # - If the true clause ends in return, make sure it's included # - If the true clause ends in RAISE_VARARGS, then it's an # assert statement. For now I just write it as a raise within # an if (see below) if end_true.opcode in (RETURN_VALUE, RAISE_VARARGS): # TODO: change # if cond: raise AssertionError(x) # to # assert cond, x d_true = SuiteDecompiler(addr[1], end_true[1]) d_true.run() self.suite.add_statement(IfStatement(cond, d_true.suite, Suite())) return jump_addr d_true = SuiteDecompiler(addr[1], end_true) d_true.run() if jump_addr.opcode == POP_BLOCK: # It's a while loop stmt = WhileStatement(cond, d_true.suite) self.suite.add_statement(stmt) return jump_addr[1] # It's an if-else (expression or statement) if end_true.opcode == JUMP_FORWARD: end_false = end_true.jump() elif end_true.opcode == JUMP_ABSOLUTE: end_false = end_true.jump() if end_false.opcode == FOR_ITER: # We are in a for-loop with nothing after the else-suite end_false = end_false.jump()[-1] elif end_false[-1].opcode == SETUP_LOOP: # We are in a while-loop with nothing after the else-suite end_false = end_false[-1].jump()[-1] elif end_true.opcode == RETURN_VALUE: # find the next RETURN_VALUE end_false = jump_addr while end_false.opcode != RETURN_VALUE: end_false = end_false[1] end_false = end_false[1] elif end_true.opcode == BREAK_LOOP: # likely in a loop in a try/except end_false = jump_addr else: # normal statement self.write("#ERROR: Unexpected statement: {} | {}\n".format(end_true, jump_addr, jump_addr[-1])) # raise Unknown jump_addr = end_true[-2] stmt = IfStatement(cond, d_true.suite, None) self.suite.add_statement(stmt) return jump_addr or self.END_NOW d_false = SuiteDecompiler(jump_addr, end_false) d_false.run() if d_true.stack and d_false.stack: assert len(d_true.stack) == len(d_false.stack) == 1 # self.write("#ERROR: Unbalanced stacks {} != {}".format(len(d_true.stack),len(d_false.stack))) assert not (d_true.suite or d_false.suite) # this happens in specific if else conditions with assigments true_expr = d_true.stack.pop() false_expr = d_false.stack.pop() self.stack.push(PyIfElse(cond, true_expr, false_expr)) else: stmt = IfStatement(cond, d_true.suite, d_false.suite) self.suite.add_statement(stmt) return end_false or self.END_NOW def POP_JUMP_IF_FALSE(self, addr, target): return self.POP_JUMP_IF(addr, target, truthiness=False) def POP_JUMP_IF_TRUE(self, addr, target): return self.POP_JUMP_IF(addr, target, truthiness=True) def JUMP_ABSOLUTE(self, addr, target): # print("*** JUMP ABSOLUTE ***", addr) #return addr.jump() # TODO: print out continue if not final jump jump_addr = addr.jump() if jump_addr[-1].opcode == SETUP_LOOP: end_addr = jump_addr + jump_addr[-1].arg last_jump = self.scan_for_final_jump(jump_addr, end_addr[-1]) if last_jump != addr: self.write("continue") pass # # For loops # def GET_ITER(self, addr): pass def FOR_ITER(self, addr, delta): iterable = self.stack.pop() jump_addr = addr.jump() d_body = SuiteDecompiler(addr[1], jump_addr[-1]) for_stmt = ForStatement(iterable) d_body.stack.push(for_stmt) d_body.run() for_stmt.body = d_body.suite self.suite.add_statement(for_stmt) return jump_addr # Function creation def MAKE_FUNCTION(self, addr, argc, is_closure=False): testType = self.stack.pop().val if isinstance(testType, str): code = Code(self.stack.pop().val, self.code) else: code = Code(testType, self.code) closure = self.stack.pop() if is_closure else None # parameter annotation objects paramobjs = {} paramcount = (argc >> 16) & 0x7FFF if paramcount: paramobjs = dict(zip(self.stack.pop().val, self.stack.pop(paramcount - 1))) # default argument objects in positional order defaults = self.stack.pop(argc & 0xFF) # pairs of name and default argument, with the name just below the object on the stack, for keyword-only parameters kwdefaults = {} for i in range((argc >> 8) & 0xFF): k, v = self.stack.pop(2) if hasattr(k, 'name'): kwdefaults[k.name] = v elif hasattr(k, 'val'): kwdefaults[k.val] = v else: kwdefaults[str(k)] = v func_maker = code_map.get(code.name, DefStatement) self.stack.push(func_maker(code, defaults, kwdefaults, closure, paramobjs)) def LOAD_CLOSURE(self, addr, i): # Push the varname. It doesn't matter as it is not used for now. self.stack.push(self.code.derefnames[i]) def MAKE_CLOSURE(self, addr, argc): self.MAKE_FUNCTION(addr, argc, is_closure=True) # # Raising exceptions # def RAISE_VARARGS(self, addr, argc): # TODO: find out when argc is 2 or 3 # Answer: In Python 3, only 0, 1, or 2 argument (see PEP 3109) if argc == 0: self.write("raise") elif argc == 1: exception = self.stack.pop() self.write("raise {}", exception) elif argc == 2: from_exc, exc = self.stack.pop(), self.stack.pop() self.write("raise {} from {}".format(exc, from_exc)) else: raise Unknown def EXTENDED_ARG(self, addr, ext): # self.write("# ERROR: {} : {}".format(addr, ext) ) pass def WITH_CLEANUP(self, addr, *args, **kwargs): # self.write("# ERROR: {} : {}".format(addr, args)) pass # Create unary operators types and opcode handlers for op, name, ptn, prec in unary_ops: name = 'Py' + name tp = type(name, (PyUnaryOp,), dict(pattern=ptn, precedence=prec)) globals()[name] = tp def method(self, addr, tp=tp): tp.instr(self.stack) setattr(SuiteDecompiler, op, method) # Create binary operators types and opcode handlers for op, name, ptn, prec, inplace_ptn in binary_ops: # Create the binary operator tp_name = 'Py' + name tp = globals().get(tp_name, None) if tp is None: tp = type(tp_name, (PyBinaryOp,), dict(pattern=ptn, precedence=prec)) globals()[tp_name] = tp def method(self, addr, tp=tp): tp.instr(self.stack) setattr(SuiteDecompiler, 'BINARY_' + op, method) # Create the in-place operation if inplace_ptn is not None: inplace_op = "INPLACE_" + op tp_name = 'InPlace' + name tp = type(tp_name, (InPlaceOp,), dict(pattern=inplace_ptn)) globals()[tp_name] = tp def method(self, addr, tp=tp): left, right = self.stack.pop(2) self.stack.push(tp(left, right)) setattr(SuiteDecompiler, inplace_op, method) if __name__ == "__main__": import sys if len(sys.argv) == 1: print('USAGE: {} '.format(sys.argv[0])) else: print(decompile(sys.argv[1]))