diff --git a/test/bytecode_3.4/05_try_except.pyc b/test/bytecode_3.4/05_try_except.pyc index 3589b2e2..27dbdb63 100644 Binary files a/test/bytecode_3.4/05_try_except.pyc and b/test/bytecode_3.4/05_try_except.pyc differ diff --git a/test/simple_source/exception/05_try_except.py b/test/simple_source/exception/05_try_except.py index 48ec82b9..5b0ebaee 100644 --- a/test/simple_source/exception/05_try_except.py +++ b/test/simple_source/exception/05_try_except.py @@ -5,6 +5,14 @@ def handle(module): module = exc return module +def handle2(module): + if module == 'foo': + try: + module = 1 + except ImportError as exc: + module = exc + return module + try: pass except ImportError as exc: diff --git a/test/simple_source/looping/10_while.py b/test/simple_source/looping/10_while.py new file mode 100644 index 00000000..a3a5878a --- /dev/null +++ b/test/simple_source/looping/10_while.py @@ -0,0 +1,2 @@ +while __name__ != '__main__': + b = 4 diff --git a/test/simple_source/looping/while.py b/test/simple_source/looping/while.py deleted file mode 100644 index 44b62ffb..00000000 --- a/test/simple_source/looping/while.py +++ /dev/null @@ -1,2 +0,0 @@ -while a: - b = c diff --git a/test/simple_source/simple_stmts/00_assign.py b/test/simple_source/stmts/00_assign.py similarity index 100% rename from test/simple_source/simple_stmts/00_assign.py rename to test/simple_source/stmts/00_assign.py diff --git a/test/simple_source/simple_stmts/00_import.py b/test/simple_source/stmts/00_import.py similarity index 100% rename from test/simple_source/simple_stmts/00_import.py rename to test/simple_source/stmts/00_import.py diff --git a/test/simple_source/simple_stmts/00_pass.py b/test/simple_source/stmts/00_pass.py similarity index 100% rename from test/simple_source/simple_stmts/00_pass.py rename to test/simple_source/stmts/00_pass.py diff --git a/test/simple_source/simple_stmts/15_assert.py b/test/simple_source/stmts/15_assert.py similarity index 100% rename from test/simple_source/simple_stmts/15_assert.py rename to test/simple_source/stmts/15_assert.py diff --git a/test/simple_source/stmts/15_for_if.py b/test/simple_source/stmts/15_for_if.py new file mode 100644 index 00000000..70f519b5 --- /dev/null +++ b/test/simple_source/stmts/15_for_if.py @@ -0,0 +1,5 @@ +if __name__: + for i in (1,2): + x = 3 + pass + pass diff --git a/uncompyle6/parsers/parse2.py b/uncompyle6/parsers/parse2.py index e3e28ec4..66e845b4 100644 --- a/uncompyle6/parsers/parse2.py +++ b/uncompyle6/parsers/parse2.py @@ -405,15 +405,15 @@ class Python2Parser(PythonParser): trystmt ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK - try_middle COME_FROM + try_middle COME_FROM # this is nested inside a trystmt tryfinallystmt ::= SETUP_FINALLY suite_stmts POP_BLOCK LOAD_CONST COME_FROM suite_stmts_opt END_FINALLY - tryelsestmt ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK - try_middle else_suite COME_FROM + tryelsestmt ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK + try_middle else_suite COME_FROM tryelsestmtc ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK try_middle else_suitec COME_FROM diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index fb5d30ae..e14b69cf 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -71,7 +71,7 @@ class Python3Parser(PythonParser): _come_from ::= COME_FROM _come_from ::= - list_for ::= expr FOR_ITER designator list_iter JUMP_ABSOLUTE + list_for ::= expr FOR_ITER designator list_iter JUMP_BACK list_if ::= expr jmp_false list_iter list_if_not ::= expr jmp_true list_iter @@ -407,7 +407,7 @@ class Python3Parser(PythonParser): testtrue ::= expr jmp_true _ifstmts_jump ::= return_if_stmts - _ifstmts_jump ::= c_stmts_opt JUMP_FORWARD COME_FROM + _ifstmts_jump ::= c_stmts_opt JUMP_FORWARD COME_FROM _come_from iflaststmt ::= testexpr c_stmts_opt JUMP_ABSOLUTE @@ -459,7 +459,7 @@ class Python3Parser(PythonParser): # This is used in Python 3 in # "except ... as e" to remove 'e' after the c_stmts_opt finishes except_suite_finalize ::= SETUP_FINALLY c_stmts_opt except_var_finalize - END_FINALLY JUMP_FORWARD + END_FINALLY _jump except_var_finalize ::= POP_BLOCK POP_EXCEPT LOAD_CONST COME_FROM LOAD_CONST designator del_stmt @@ -490,7 +490,7 @@ class Python3Parser(PythonParser): whilestmt ::= SETUP_LOOP testexpr - l_stmts_opt JUMP_ABSOLUTE + l_stmts_opt JUMP_BACK POP_BLOCK COME_FROM whilestmt ::= SETUP_LOOP @@ -515,11 +515,11 @@ class Python3Parser(PythonParser): _for ::= GET_ITER FOR_ITER _for ::= LOAD_CONST FOR_LOOP - for_block ::= l_stmts_opt JUMP_ABSOLUTE + for_block ::= l_stmts_opt JUMP_BACK for_block ::= return_stmts _come_from forstmt ::= SETUP_LOOP expr _for designator - for_block POP_BLOCK COME_FROM + for_block POP_BLOCK _come_from forelsestmt ::= SETUP_LOOP expr _for designator for_block POP_BLOCK else_suite COME_FROM diff --git a/uncompyle6/scanners/scanner32.py b/uncompyle6/scanners/scanner32.py index 11b1c314..148b300b 100644 --- a/uncompyle6/scanners/scanner32.py +++ b/uncompyle6/scanners/scanner32.py @@ -34,11 +34,11 @@ class Scanner32(scan.Scanner): tokens = self.tokenize(code_object) return tokens - def disassemble(self, co): + def disassemble(self, co, classname=None): """ Convert code object into a sequence of tokens. - Based on dis.disassemble() function. + The below is based on (an older version?) of Python dis.disassemble_bytes(). """ # Container for tokens tokens = [] @@ -47,9 +47,64 @@ class Scanner32(scan.Scanner): codelen = len(code) self.build_lines_data(co) self.build_prev_op() + # Get jump targets # Format: {target offset: [jump offsets]} jump_targets = self.find_jump_targets() + + # self.lines contains (block,addrLastInstr) + if classname: + classname = '_' + classname.lstrip('_') + '__' + + def unmangle(name): + if name.startswith(classname) and name[-2:] != '__': + return name[len(classname) - 2:] + return name + + # free = [ unmangle(name) for name in (co.co_cellvars + co.co_freevars) ] + # names = [ unmangle(name) for name in co.co_names ] + # varnames = [ unmangle(name) for name in co.co_varnames ] + else: + # free = co.co_cellvars + co.co_freevars + # names = co.co_names + # varnames = co.co_varnames + pass + + # Scan for assertions. Later we will + # turn 'LOAD_GLOBAL' to 'LOAD_ASSERT' for those + # assertions + + self.load_asserts = set() + for i in self.op_range(0, codelen): + if self.code[i] == POP_JUMP_IF_TRUE and self.code[i+3] == LOAD_GLOBAL: + if names[self.get_argument(i+3)] == 'AssertionError': + self.load_asserts.add(i+3) + + # FIXME: reinstate code + # cf = self.find_jump_targets(self.code) + # # contains (code, [addrRefToCode]) + # last_stmt = self.next_stmt[0] + # i = self.next_stmt[last_stmt] + # replace = {} + # while i < n-1: + # if self.lines[last_stmt].next > i: + # if self.code[last_stmt] == PRINT_ITEM: + # if self.code[i] == PRINT_ITEM: + # replace[i] = 'PRINT_ITEM_CONT' + # elif self.code[i] == PRINT_NEWLINE: + # replace[i] = 'PRINT_NEWLINE_CONT' + # last_stmt = i + # i = self.next_stmt[i] + + # imports = self.all_instr(0, n, (IMPORT_NAME, IMPORT_FROM, IMPORT_STAR)) + # if len(imports) > 1: + # last_import = imports[0] + # for i in imports[1:]: + # if self.lines[last_import].next > i: + # if self.code[last_import] == IMPORT_NAME == self.code[i]: + # replace[i] = 'IMPORT_NAME_CONT' + # last_import = i + # Initialize extended arg at 0. When extended arg op is encountered, # variable preserved for next cycle and added as arg for next op extended_arg = 0 @@ -63,6 +118,8 @@ class Scanner32(scan.Scanner): offset='{}_{}'.format(offset, jump_idx))) jump_idx += 1 op = code[offset] + op_name = opname[op] + # Create token and fill all the fields we can # w/o touching arguments current_token = Token(dis.opname[op]) @@ -97,6 +154,17 @@ class Scanner32(scan.Scanner): if free is None: free = co.co_cellvars + co.co_freevars current_token.pattr = free[oparg] + if op == JUMP_ABSOLUTE: + current_token.pattr = current_token.attr = oparg + target = self.get_target(offset) + if target < offset: + if offset in self.stmts and self.code[offset+3] not in (END_FINALLY, POP_BLOCK) \ + and offset not in self.not_continue: + op_name = 'CONTINUE' + else: + op_name = 'JUMP_BACK' + current_token.type = op_name + tokens.append(current_token) return tokens, customize diff --git a/uncompyle6/scanners/scanner34.py b/uncompyle6/scanners/scanner34.py index bc909649..be1ce321 100644 --- a/uncompyle6/scanners/scanner34.py +++ b/uncompyle6/scanners/scanner34.py @@ -20,7 +20,7 @@ from collections import namedtuple from array import array from uncompyle6 import PYTHON_VERSION -from uncompyle6.scanner import Token, L65536 +from uncompyle6.scanner import Token import uncompyle6.opcodes.opcode_34 # Get all the opcodes into globals @@ -145,6 +145,17 @@ class Scanner34(scan.Scanner): if inst.opname != 'BUILD_SLICE': customize[opname] = inst.argval + elif opname == 'JUMP_ABSOLUTE': + pattr = inst.argval + target = self.get_target(inst.offset) + if target < inst.offset: + if (inst.offset in self.stmts and + self.code[inst.offset+3] not in (END_FINALLY, POP_BLOCK) + and offset not in self.not_continue): + opname = 'CONTINUE' + else: + opname = 'JUMP_BACK' + elif inst.offset in self.load_asserts: opname = 'LOAD_ASSERT' @@ -160,71 +171,8 @@ class Scanner34(scan.Scanner): pass return tokens, {} - def disassemble_cross_version(self, co): - """ - Convert code object into a sequence of tokens - FIXME: the below is not based on the current Python 3.4 dis.disassemble_bytes(). - Fix that. - """ - # Container for tokens - tokens = [] - customize = {} - self.code = code = array('B', co.co_code) - codelen = len(code) - self.build_lines_data(co) - self.build_prev_op() - # Get jump targets - # Format: {target offset: [jump offsets]} - jump_targets = self.find_jump_targets() - # Initialize extended arg at 0. When extended arg op is encountered, - # variable preserved for next cycle and added as arg for next op - extended_arg = 0 - free = None - for offset in self.op_range(0, codelen): - # Add jump target tokens - if offset in jump_targets: - jump_idx = 0 - for jump_offset in jump_targets[offset]: - tokens.append(Token('COME_FROM', None, repr(jump_offset), - offset='{}_{}'.format(offset, jump_idx))) - jump_idx += 1 - op = code[offset] - # Create token and fill all the fields we can - # w/o touching arguments - current_token = Token(dis.opname[op]) - current_token.offset = offset - - if offset in self.linestarts: - current_token.linestart = self.linestarts[offset] - else: - current_token.linestart = None - - if op >= dis.HAVE_ARGUMENT: - # Calculate op's argument value based on its argument and - # preceding extended argument, if any - oparg = code[offset+1] + code[offset+2]*256 + extended_arg - extended_arg = 0 - if op == dis.EXTENDED_ARG: - extended_arg = oparg * L65536 - - # Fill token's attr/pattr fields - current_token.attr = oparg - if op in dis.hasconst: - current_token.pattr = repr(co.co_consts[oparg]) - elif op in dis.hasname: - current_token.pattr = co.co_names[oparg] - elif op in dis.hasjrel: - current_token.pattr = repr(offset + 3 + oparg) - elif op in dis.haslocal: - current_token.pattr = co.co_varnames[oparg] - elif op in dis.hascompare: - current_token.pattr = dis.cmp_op[oparg] - elif op in dis.hasfree: - if free is None: - free = co.co_cellvars + co.co_freevars - current_token.pattr = free[oparg] - tokens.append(current_token) - return tokens, customize + def disassemble_cross_version(self, co, classname=None): + return scan.scanner32().disassemble(self, co, classname) def build_lines_data(self, code_obj): """