Fix 3.5 misclassifying RETURN_VALUE

We use location of SETUP_EXCEPT instructions to disambiguate.
This commit is contained in:
rocky
2016-07-29 08:56:23 -04:00
parent 152935ab26
commit 3a6f9d8f24
5 changed files with 30 additions and 13 deletions

View File

@@ -40,8 +40,6 @@ class Python35Parser(Python3Parser):
# RETURN_END_IF vs RETURN_VALUE # RETURN_END_IF vs RETURN_VALUE
ifelsestmtc ::= testexpr c_stmts_opt JUMP_FORWARD else_suitec ifelsestmtc ::= testexpr c_stmts_opt JUMP_FORWARD else_suitec
return_stmt ::= ret_expr RETURN_END_IF
# Python 3.3+ also has yield from. 3.5 does it # Python 3.3+ also has yield from. 3.5 does it
# differently than 3.3, 3.4 # differently than 3.3, 3.4

View File

@@ -251,6 +251,8 @@ class Scanner(object):
self.Token = tokenClass self.Token = tokenClass
return self.Token return self.Token
def op_has_argument(op, opc):
return op >= opc.HAVE_ARGUMENT
def parse_fn_counts(argc): def parse_fn_counts(argc):
return ((argc & 0xFF), (argc >> 8) & 0xFF, (argc >> 16) & 0x7FFF) return ((argc & 0xFF), (argc >> 8) & 0xFF, (argc >> 16) & 0x7FFF)

View File

@@ -25,6 +25,7 @@ from __future__ import print_function
from collections import namedtuple from collections import namedtuple
from array import array from array import array
from uncompyle6.scanner import Scanner, op_has_argument
from xdis.code import iscode from xdis.code import iscode
from xdis.bytecode import Bytecode from xdis.bytecode import Bytecode
from uncompyle6.scanner import Token, parse_fn_counts from uncompyle6.scanner import Token, parse_fn_counts
@@ -42,9 +43,7 @@ globals().update(op3.opmap)
# POP_JUMP_IF is used by verify # POP_JUMP_IF is used by verify
POP_JUMP_TF = (POP_JUMP_IF_TRUE, POP_JUMP_IF_FALSE) POP_JUMP_TF = (POP_JUMP_IF_TRUE, POP_JUMP_IF_FALSE)
import uncompyle6.scanner as scan class Scanner3(Scanner):
class Scanner3(scan.Scanner):
def __init__(self, version, show_asm=None, is_pypy=False): def __init__(self, version, show_asm=None, is_pypy=False):
super(Scanner3, self).__init__(version, show_asm, is_pypy) super(Scanner3, self).__init__(version, show_asm, is_pypy)
@@ -234,7 +233,7 @@ class Scanner3(scan.Scanner):
offset = inst.offset, offset = inst.offset,
linestart = inst.starts_line, linestart = inst.starts_line,
op = op, op = op,
has_arg = (op >= op3.HAVE_ARGUMENT), has_arg = op_has_argument(op, op3),
opc = self.opc opc = self.opc
) )
) )
@@ -403,7 +402,7 @@ class Scanner3(scan.Scanner):
# Determine structures and fix jumps in Python versions # Determine structures and fix jumps in Python versions
# since 2.3 # since 2.3
self.detect_structure(offset) self.detect_structure(offset, targets)
has_arg = (op >= op3.HAVE_ARGUMENT) has_arg = (op >= op3.HAVE_ARGUMENT)
if has_arg: if has_arg:
@@ -517,7 +516,7 @@ class Scanner3(scan.Scanner):
target += offset + 3 target += offset + 3
return target return target
def detect_structure(self, offset): def detect_structure(self, offset, targets):
""" """
Detect structures and their boundaries to fix optimized jumps Detect structures and their boundaries to fix optimized jumps
in python2.3+ in python2.3+
@@ -735,10 +734,33 @@ class Scanner3(scan.Scanner):
self.structs.append({'type': 'if-then', self.structs.append({'type': 'if-then',
'start': start, 'start': start,
'end': rtarget}) 'end': rtarget})
# It is important to distingish if this return is inside some sort
# except block return
jump_prev = prev_op[offset] jump_prev = prev_op[offset]
if self.is_pypy and code[jump_prev] == self.opc.COMPARE_OP: if self.is_pypy and code[jump_prev] == self.opc.COMPARE_OP:
if self.opc.cmp_op[code[jump_prev+1]] == 'exception match': if self.opc.cmp_op[code[jump_prev+1]] == 'exception match':
return return
if self.version >= 3.5:
# Python 3.5 may remove as dead code a JUMP
# instruction after a RETURN_VALUE. So we check
# based on seeing SETUP_EXCEPT various places.
if code[rtarget] == self.opc.SETUP_EXCEPT:
return
# Check that next instruction after pops and jump is
# not from SETUP_EXCEPT
next_op = rtarget
if code[next_op] == self.opc.POP_BLOCK:
next_op += self.op_size(self.code[next_op])
if code[next_op] == self.opc.JUMP_ABSOLUTE:
next_op += self.op_size(self.code[next_op])
if next_op in targets:
for try_op in targets[next_op]:
come_from_op = code[try_op]
if come_from_op == self.opc.SETUP_EXCEPT:
return
pass
pass
pass
self.return_end_ifs.add(prev_op[rtarget]) self.return_end_ifs.add(prev_op[rtarget])
elif op in self.jump_if_pop: elif op in self.jump_if_pop:

View File

@@ -739,11 +739,6 @@ class SourceWalker(GenericASTTraversal, object):
if self.return_none or node != AST('return_stmt', [AST('ret_expr', [NONE]), Token('RETURN_VALUE')]): if self.return_none or node != AST('return_stmt', [AST('ret_expr', [NONE]), Token('RETURN_VALUE')]):
self.write(' ') self.write(' ')
self.preorder(node[0]) self.preorder(node[0])
# 3.5 does jump optimization. The RETURN_END_IF in the return
# statement means to dedent. Earlier versions will just have
# RETURN_VALUE it is done by a nonterminal in the grammar.
if self.version >= 3.5 and node[-1] == 'RETURN_END_IF':
self.indentLess()
self.println() self.println()
self.prune() # stop recursing self.prune() # stop recursing