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:
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):

View File

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

View File

@@ -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,55 +577,61 @@ 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)
@@ -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,8 +970,17 @@ 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
# 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