diff --git a/test/bytecode_2.7/05_while_if_return.pyc b/test/bytecode_2.7/05_while_if_return.pyc new file mode 100644 index 00000000..079229de Binary files /dev/null and b/test/bytecode_2.7/05_while_if_return.pyc differ diff --git a/test/simple_source/bug27+/05_while_if_return.py b/test/simple_source/bug27+/05_while_if_return.py new file mode 100644 index 00000000..7688f9f9 --- /dev/null +++ b/test/simple_source/bug27+/05_while_if_return.py @@ -0,0 +1,10 @@ +# Issue #149. Bug in Python 2.7 was handling a return stmt at the end +# of a while with so no jump back, confusing which block the +# return should be part of +def test(a): + while True: + if a: + pass + else: + continue + return diff --git a/uncompyle6/bin/uncompile.py b/uncompyle6/bin/uncompile.py index 001de8da..4b5c875e 100755 --- a/uncompyle6/bin/uncompile.py +++ b/uncompyle6/bin/uncompile.py @@ -171,8 +171,9 @@ def main_bin(): try: result = main(src_base, out_base, files, codes, outfile, **options) + result = list(result) + [options.get('do_verify', None)] if len(files) > 1: - mess = status_msg(do_verify, result, do_verify) + mess = status_msg(do_verify, *result) print('# ' + mess) pass except (KeyboardInterrupt): diff --git a/uncompyle6/parsers/parse27.py b/uncompyle6/parsers/parse27.py index 257ac5ed..e5f59cb3 100644 --- a/uncompyle6/parsers/parse27.py +++ b/uncompyle6/parsers/parse27.py @@ -75,7 +75,7 @@ class Python27Parser(Python2Parser): ret_and ::= expr JUMP_IF_FALSE_OR_POP ret_expr_or_cond COME_FROM ret_or ::= expr JUMP_IF_TRUE_OR_POP ret_expr_or_cond COME_FROM - ret_cond ::= expr POP_JUMP_IF_FALSE expr RETURN_END_IF COME_FROM ret_expr_or_cond + ret_cond ::= expr POP_JUMP_IF_FALSE expr RETURN_VALUE COME_FROM ret_expr_or_cond or ::= expr JUMP_IF_TRUE_OR_POP expr COME_FROM and ::= expr JUMP_IF_FALSE_OR_POP expr COME_FROM @@ -119,7 +119,8 @@ class Python27Parser(Python2Parser): POP_BLOCK LOAD_CONST COME_FROM_WITH WITH_CLEANUP END_FINALLY - whilestmt ::= SETUP_LOOP testexpr returns POP_BLOCK COME_FROM + whilestmt ::= SETUP_LOOP testexpr returns + _come_froms POP_BLOCK COME_FROM while1stmt ::= SETUP_LOOP returns bp_come_from while1stmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK COME_FROM @@ -135,8 +136,16 @@ class Python27Parser(Python2Parser): ifelsestmtl ::= testexpr c_stmts_opt JUMP_BACK else_suitel ifelsestmtl ::= testexpr c_stmts_opt CONTINUE else_suitel + # These below rules in 2.6 and before use RETURN_IF_THEN.. insead + # of RETURN_VALUE.. However since our "if" non-if detection in + # 2.7 is weak, we allow RETURN_VALUE as well as RETURN_IF_THEN + + return_if_lambda ::= RETURN_VALUE_LAMBDA COME_FROM + conditional_lambda ::= expr jmp_false expr return_lambda + return_stmt_lambda LAMBDA_MARKER + return_if_stmt ::= ret_expr RETURN_VALUE + # Common with 2.6 - return_if_lambda ::= RETURN_END_IF_LAMBDA COME_FROM stmt ::= conditional_lambda conditional_lambda ::= expr jmp_false expr return_if_lambda return_stmt_lambda LAMBDA_MARKER diff --git a/uncompyle6/scanners/scanner2.py b/uncompyle6/scanners/scanner2.py index 40235e31..5ae6dc4d 100644 --- a/uncompyle6/scanners/scanner2.py +++ b/uncompyle6/scanners/scanner2.py @@ -26,7 +26,9 @@ from collections import namedtuple from array import array from xdis.code import iscode -from xdis.bytecode import Bytecode, op_has_argument, op_size, instruction_size +from xdis.bytecode import ( + Bytecode, op_has_argument, op_size, + instruction_size, get_jump_targets) from xdis.util import code2num from uncompyle6.scanner import Scanner @@ -142,6 +144,7 @@ class Scanner2(Scanner): self.load_asserts.add(i+3) jump_targets = self.find_jump_targets(show_asm) + self.xdis_jump_targets = get_jump_targets(co, self.opc) # contains (code, [addrRefToCode]) last_stmt = self.next_stmt[0] @@ -512,49 +515,53 @@ class Scanner2(Scanner): inst = self.insts[self.offset2inst_index[offset]] start += instruction_size(op, self.opc) - target = inst.argval - end_offset = self.restrict_to_parent(target, parent) - self.setup_loop_targets[offset] = target - self.setup_loops[target] = offset + setup_target = inst.argval + loop_end_offset = self.restrict_to_parent(setup_target, parent) + self.setup_loop_targets[offset] = setup_target + self.setup_loops[setup_target] = offset - if target != end_offset: - self.fixed_jumps[offset] = end_offset + if setup_target != loop_end_offset: + self.fixed_jumps[offset] = loop_end_offset (line_no, next_line_byte) = self.lines[offset] - jump_back = self.last_instr(start, end_offset, self.opc.JUMP_ABSOLUTE, + + # jump_back_offset is the instruction after the SETUP_LOOP + # where we iterate back to. + jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.JUMP_ABSOLUTE, next_line_byte, False) - if jump_back: + if jump_back_offset: # Account for the fact that < 2.7 has an explicit # POP_TOP instruction in the equivalate POP_JUMP_IF # construct if self.version < 2.7: - jump_forward_offset = jump_back+4 - return_val_offset1 = self.prev[self.prev[self.prev[end_offset]]] + jump_forward_offset = jump_back_offset+4 + return_val_offset1 = self.prev[self.prev[self.prev[loop_end_offset]]] # Is jump back really "back"? - jump_target = self.get_target(jump_back, code[jump_back]) - if (jump_target > jump_back or - code[jump_back+3] in [self.opc.JUMP_FORWARD, self.opc.JUMP_ABSOLUTE]): - jump_back = None + jump_target = self.get_target(jump_back_offset, code[jump_back_offset]) + if (jump_target > jump_back_offset or + code[jump_back_offset+3] in [self.opc.JUMP_FORWARD, self.opc.JUMP_ABSOLUTE]): + jump_back_offset = None pass else: - jump_forward_offset = jump_back+3 - return_val_offset1 = self.prev[self.prev[end_offset]] + jump_forward_offset = jump_back_offset+3 + return_val_offset1 = self.prev[self.prev[loop_end_offset]] - if (jump_back and jump_back != self.prev[end_offset] + if (jump_back_offset and jump_back_offset != self.prev[loop_end_offset] and code[jump_forward_offset] in self.jump_forward): - if (code[self.prev[end_offset]] == self.opc.RETURN_VALUE or - (code[self.prev[end_offset]] == self.opc.POP_BLOCK + if (code[self.prev[loop_end_offset]] == self.opc.RETURN_VALUE or + (code[self.prev[loop_end_offset]] == self.opc.POP_BLOCK and code[return_val_offset1] == self.opc.RETURN_VALUE)): - jump_back = None - if not jump_back: + jump_back_offset = None + + if not jump_back_offset: # loop suite ends in return # scanner26 of wbiti had: - # jump_back = self.last_instr(start, end_offset, self.opc.JUMP_ABSOLUTE, start, False) - jump_back = self.last_instr(start, end_offset, self.opc.RETURN_VALUE) - if not jump_back: + # jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.JUMP_ABSOLUTE, start, False) + jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.RETURN_VALUE) + if not jump_back_offset: return - jump_back += 1 + jump_back_offset += 1 if_offset = None if self.version < 2.7: @@ -570,59 +577,65 @@ class Scanner2(Scanner): loop_type = 'while' self.ignore_if.add(if_offset) if self.version < 2.7 and ( - code[self.prev[jump_back]] == self.opc.RETURN_VALUE): - self.ignore_if.add(self.prev[jump_back]) + code[self.prev[jump_back_offset]] == self.opc.RETURN_VALUE): + self.ignore_if.add(self.prev[jump_back_offset]) pass pass else: loop_type = 'for' - target = next_line_byte - end_offset = jump_back + 3 + setup_target = next_line_byte + loop_end_offset = jump_back_offset + 3 else: - if self.get_target(jump_back) >= next_line_byte: - jump_back = self.last_instr(start, end_offset, self.opc.JUMP_ABSOLUTE, start, False) - if end_offset > jump_back+4 and code[end_offset] in self.jump_forward: - if code[jump_back+4] in self.jump_forward: - if self.get_target(jump_back+4) == self.get_target(end_offset): - self.fixed_jumps[offset] = jump_back+4 - end_offset = jump_back+4 - elif target < offset: - self.fixed_jumps[offset] = jump_back+4 - end_offset = jump_back+4 + # We have a loop with a jump-back instruction + if self.get_target(jump_back_offset) >= next_line_byte: + jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.JUMP_ABSOLUTE, start, False) + if loop_end_offset > jump_back_offset+4 and code[loop_end_offset] in self.jump_forward: + if code[jump_back_offset+4] in self.jump_forward: + if self.get_target(jump_back_offset+4) == self.get_target(loop_end_offset): + self.fixed_jumps[offset] = jump_back_offset+4 + loop_end_offset = jump_back_offset+4 + elif setup_target < offset: + self.fixed_jumps[offset] = jump_back_offset+4 + loop_end_offset = jump_back_offset+4 - target = self.get_target(jump_back, self.opc.JUMP_ABSOLUTE) + setup_target = self.get_target(jump_back_offset, self.opc.JUMP_ABSOLUTE) if (self.version > 2.1 and - code[target] in (self.opc.FOR_ITER, self.opc.GET_ITER)): + code[setup_target] in (self.opc.FOR_ITER, self.opc.GET_ITER)): loop_type = 'for' else: loop_type = 'while' + # Look for a test condition immediately after the + # SETUP_LOOP while if (self.version < 2.7 and self.code[self.prev[next_line_byte]] == self.opc.POP_TOP): - test = self.prev[self.prev[next_line_byte]] + test_op_offset = self.prev[self.prev[next_line_byte]] else: - test = self.prev[next_line_byte] + test_op_offset = self.prev[next_line_byte] - if test == offset: + if test_op_offset == offset: loop_type = 'while 1' - elif self.code[test] in self.opc.JUMP_OPs: - self.ignore_if.add(test) - test_target = self.get_target(test) - if test_target > (jump_back+3): - jump_back = test_target - self.not_continue.add(jump_back) - self.loops.append(target) + elif self.code[test_op_offset] in self.opc.JUMP_OPs: + test_target = self.get_target(test_op_offset) + + if self.version < 2.7: + self.ignore_if.add(test_op_offset) + + if test_target > (jump_back_offset+3): + jump_back_offset = test_target + self.not_continue.add(jump_back_offset) + self.loops.append(setup_target) self.structs.append({'type': loop_type + '-loop', - 'start': target, - 'end': jump_back}) - if jump_back+3 != end_offset: + 'start': setup_target, + 'end': jump_back_offset}) + if jump_back_offset+3 != loop_end_offset: self.structs.append({'type': loop_type + '-else', - 'start': jump_back+3, - 'end': end_offset}) + 'start': jump_back_offset+3, + 'end': loop_end_offset}) elif op == self.opc.SETUP_EXCEPT: start = offset + op_size(op, self.opc) target = self.get_target(offset, op) - end_offset = self.restrict_to_parent(target, parent) + end_offset = self.restrict_to_parent(target, parent) if target != end_offset: self.fixed_jumps[offset] = end_offset # print target, end, parent @@ -837,7 +850,7 @@ class Scanner2(Scanner): # JUMP_FORWARD # HERE: # - # If so, this can be block inside an "if" statement + # If so, this can be a block inside an "if" statement # or a conditional assignment like: # x = 1 if x else 2 # @@ -957,9 +970,18 @@ class Scanner2(Scanner): 'start': start, 'end': rtarget}) self.thens[start] = rtarget - if self.version == 2.7 or code[pre_rtarget+1] != self.opc.JUMP_FORWARD: + if ((self.version == 2.7 and target > offset) + or (self.version < 2.7 and + (code[pre_rtarget+1] != self.opc.JUMP_FORWARD))): self.fixed_jumps[offset] = rtarget - self.return_end_ifs.add(pre_rtarget) + + # We need more sophistication in + # determining whether this is an end if in + # 2.7. For now, skip here, but we have + # jiggered the grammar (parse27.py) to + # treat the two more alike. + if self.version < 2.7: + self.return_end_ifs.add(pre_rtarget) pass pass pass