2.7 control flow futzing.

Some overall cleanup. But again we need to attack all of this more head on.

Closes Issue #149
This commit is contained in:
rocky
2018-02-04 14:20:11 -05:00
parent 296fcd89ce
commit 4cbba3d46e
5 changed files with 107 additions and 65 deletions

Binary file not shown.

View File

@@ -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

View File

@@ -171,8 +171,9 @@ def main_bin():
try: try:
result = main(src_base, out_base, files, codes, outfile, result = main(src_base, out_base, files, codes, outfile,
**options) **options)
result = list(result) + [options.get('do_verify', None)]
if len(files) > 1: if len(files) > 1:
mess = status_msg(do_verify, result, do_verify) mess = status_msg(do_verify, *result)
print('# ' + mess) print('# ' + mess)
pass pass
except (KeyboardInterrupt): except (KeyboardInterrupt):

View File

@@ -75,7 +75,7 @@ class Python27Parser(Python2Parser):
ret_and ::= expr JUMP_IF_FALSE_OR_POP ret_expr_or_cond COME_FROM 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_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 or ::= expr JUMP_IF_TRUE_OR_POP expr COME_FROM
and ::= expr JUMP_IF_FALSE_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 POP_BLOCK LOAD_CONST COME_FROM_WITH
WITH_CLEANUP END_FINALLY 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 returns bp_come_from
while1stmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK 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 JUMP_BACK else_suitel
ifelsestmtl ::= testexpr c_stmts_opt CONTINUE 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 # Common with 2.6
return_if_lambda ::= RETURN_END_IF_LAMBDA COME_FROM
stmt ::= conditional_lambda stmt ::= conditional_lambda
conditional_lambda ::= expr jmp_false expr return_if_lambda conditional_lambda ::= expr jmp_false expr return_if_lambda
return_stmt_lambda LAMBDA_MARKER return_stmt_lambda LAMBDA_MARKER

View File

@@ -26,7 +26,9 @@ from collections import namedtuple
from array import array from array import array
from xdis.code import iscode 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 xdis.util import code2num
from uncompyle6.scanner import Scanner from uncompyle6.scanner import Scanner
@@ -142,6 +144,7 @@ class Scanner2(Scanner):
self.load_asserts.add(i+3) self.load_asserts.add(i+3)
jump_targets = self.find_jump_targets(show_asm) jump_targets = self.find_jump_targets(show_asm)
self.xdis_jump_targets = get_jump_targets(co, self.opc)
# contains (code, [addrRefToCode]) # contains (code, [addrRefToCode])
last_stmt = self.next_stmt[0] last_stmt = self.next_stmt[0]
@@ -512,49 +515,53 @@ class Scanner2(Scanner):
inst = self.insts[self.offset2inst_index[offset]] inst = self.insts[self.offset2inst_index[offset]]
start += instruction_size(op, self.opc) start += instruction_size(op, self.opc)
target = inst.argval setup_target = inst.argval
end_offset = self.restrict_to_parent(target, parent) loop_end_offset = self.restrict_to_parent(setup_target, parent)
self.setup_loop_targets[offset] = target self.setup_loop_targets[offset] = setup_target
self.setup_loops[target] = offset self.setup_loops[setup_target] = offset
if target != end_offset: if setup_target != loop_end_offset:
self.fixed_jumps[offset] = end_offset self.fixed_jumps[offset] = loop_end_offset
(line_no, next_line_byte) = self.lines[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) next_line_byte, False)
if jump_back: if jump_back_offset:
# Account for the fact that < 2.7 has an explicit # Account for the fact that < 2.7 has an explicit
# POP_TOP instruction in the equivalate POP_JUMP_IF # POP_TOP instruction in the equivalate POP_JUMP_IF
# construct # construct
if self.version < 2.7: if self.version < 2.7:
jump_forward_offset = jump_back+4 jump_forward_offset = jump_back_offset+4
return_val_offset1 = self.prev[self.prev[self.prev[end_offset]]] return_val_offset1 = self.prev[self.prev[self.prev[loop_end_offset]]]
# Is jump back really "back"? # Is jump back really "back"?
jump_target = self.get_target(jump_back, code[jump_back]) jump_target = self.get_target(jump_back_offset, code[jump_back_offset])
if (jump_target > jump_back or if (jump_target > jump_back_offset or
code[jump_back+3] in [self.opc.JUMP_FORWARD, self.opc.JUMP_ABSOLUTE]): code[jump_back_offset+3] in [self.opc.JUMP_FORWARD, self.opc.JUMP_ABSOLUTE]):
jump_back = None jump_back_offset = None
pass pass
else: else:
jump_forward_offset = jump_back+3 jump_forward_offset = jump_back_offset+3
return_val_offset1 = self.prev[self.prev[end_offset]] 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): and code[jump_forward_offset] in self.jump_forward):
if (code[self.prev[end_offset]] == self.opc.RETURN_VALUE or if (code[self.prev[loop_end_offset]] == self.opc.RETURN_VALUE or
(code[self.prev[end_offset]] == self.opc.POP_BLOCK (code[self.prev[loop_end_offset]] == self.opc.POP_BLOCK
and code[return_val_offset1] == self.opc.RETURN_VALUE)): and code[return_val_offset1] == self.opc.RETURN_VALUE)):
jump_back = None jump_back_offset = None
if not jump_back:
if not jump_back_offset:
# loop suite ends in return # loop suite ends in return
# scanner26 of wbiti had: # scanner26 of wbiti had:
# jump_back = self.last_instr(start, end_offset, self.opc.JUMP_ABSOLUTE, start, False) # jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.JUMP_ABSOLUTE, start, False)
jump_back = self.last_instr(start, end_offset, self.opc.RETURN_VALUE) jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.RETURN_VALUE)
if not jump_back: if not jump_back_offset:
return return
jump_back += 1 jump_back_offset += 1
if_offset = None if_offset = None
if self.version < 2.7: if self.version < 2.7:
@@ -570,59 +577,65 @@ class Scanner2(Scanner):
loop_type = 'while' loop_type = 'while'
self.ignore_if.add(if_offset) self.ignore_if.add(if_offset)
if self.version < 2.7 and ( if self.version < 2.7 and (
code[self.prev[jump_back]] == self.opc.RETURN_VALUE): code[self.prev[jump_back_offset]] == self.opc.RETURN_VALUE):
self.ignore_if.add(self.prev[jump_back]) self.ignore_if.add(self.prev[jump_back_offset])
pass pass
pass pass
else: else:
loop_type = 'for' loop_type = 'for'
target = next_line_byte setup_target = next_line_byte
end_offset = jump_back + 3 loop_end_offset = jump_back_offset + 3
else: else:
if self.get_target(jump_back) >= next_line_byte: # We have a loop with a jump-back instruction
jump_back = self.last_instr(start, end_offset, self.opc.JUMP_ABSOLUTE, start, False) if self.get_target(jump_back_offset) >= next_line_byte:
if end_offset > jump_back+4 and code[end_offset] in self.jump_forward: jump_back_offset = self.last_instr(start, loop_end_offset, self.opc.JUMP_ABSOLUTE, start, False)
if code[jump_back+4] in self.jump_forward: if loop_end_offset > jump_back_offset+4 and code[loop_end_offset] in self.jump_forward:
if self.get_target(jump_back+4) == self.get_target(end_offset): if code[jump_back_offset+4] in self.jump_forward:
self.fixed_jumps[offset] = jump_back+4 if self.get_target(jump_back_offset+4) == self.get_target(loop_end_offset):
end_offset = jump_back+4 self.fixed_jumps[offset] = jump_back_offset+4
elif target < offset: loop_end_offset = jump_back_offset+4
self.fixed_jumps[offset] = jump_back+4 elif setup_target < offset:
end_offset = jump_back+4 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 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' loop_type = 'for'
else: else:
loop_type = 'while' loop_type = 'while'
# Look for a test condition immediately after the
# SETUP_LOOP while
if (self.version < 2.7 if (self.version < 2.7
and self.code[self.prev[next_line_byte]] == self.opc.POP_TOP): 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: 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' loop_type = 'while 1'
elif self.code[test] in self.opc.JUMP_OPs: elif self.code[test_op_offset] in self.opc.JUMP_OPs:
self.ignore_if.add(test) test_target = self.get_target(test_op_offset)
test_target = self.get_target(test)
if test_target > (jump_back+3): if self.version < 2.7:
jump_back = test_target self.ignore_if.add(test_op_offset)
self.not_continue.add(jump_back)
self.loops.append(target) 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', self.structs.append({'type': loop_type + '-loop',
'start': target, 'start': setup_target,
'end': jump_back}) 'end': jump_back_offset})
if jump_back+3 != end_offset: if jump_back_offset+3 != loop_end_offset:
self.structs.append({'type': loop_type + '-else', self.structs.append({'type': loop_type + '-else',
'start': jump_back+3, 'start': jump_back_offset+3,
'end': end_offset}) 'end': loop_end_offset})
elif op == self.opc.SETUP_EXCEPT: elif op == self.opc.SETUP_EXCEPT:
start = offset + op_size(op, self.opc) start = offset + op_size(op, self.opc)
target = self.get_target(offset, op) 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: if target != end_offset:
self.fixed_jumps[offset] = end_offset self.fixed_jumps[offset] = end_offset
# print target, end, parent # print target, end, parent
@@ -837,7 +850,7 @@ class Scanner2(Scanner):
# JUMP_FORWARD # JUMP_FORWARD
# HERE: # 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: # or a conditional assignment like:
# x = 1 if x else 2 # x = 1 if x else 2
# #
@@ -957,9 +970,18 @@ class Scanner2(Scanner):
'start': start, 'start': start,
'end': rtarget}) 'end': rtarget})
self.thens[start] = 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.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 pass
pass pass