From 3a6f9d8f2483b3ef22aae34aaf542e3d9548021a Mon Sep 17 00:00:00 2001 From: rocky Date: Fri, 29 Jul 2016 08:56:23 -0400 Subject: [PATCH] Fix 3.5 misclassifying RETURN_VALUE We use location of SETUP_EXCEPT instructions to disambiguate. --- ...rn_bug.pyc-notyet => 07_if_return_bug.pyc} | Bin uncompyle6/parsers/parse35.py | 2 -- uncompyle6/scanner.py | 2 ++ uncompyle6/scanners/scanner3.py | 34 ++++++++++++++---- uncompyle6/semantics/pysource.py | 5 --- 5 files changed, 30 insertions(+), 13 deletions(-) rename test/bytecode_3.5/{07_if_return_bug.pyc-notyet => 07_if_return_bug.pyc} (100%) diff --git a/test/bytecode_3.5/07_if_return_bug.pyc-notyet b/test/bytecode_3.5/07_if_return_bug.pyc similarity index 100% rename from test/bytecode_3.5/07_if_return_bug.pyc-notyet rename to test/bytecode_3.5/07_if_return_bug.pyc diff --git a/uncompyle6/parsers/parse35.py b/uncompyle6/parsers/parse35.py index 5a328197..66407ca2 100644 --- a/uncompyle6/parsers/parse35.py +++ b/uncompyle6/parsers/parse35.py @@ -40,8 +40,6 @@ class Python35Parser(Python3Parser): # RETURN_END_IF vs RETURN_VALUE 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 # differently than 3.3, 3.4 diff --git a/uncompyle6/scanner.py b/uncompyle6/scanner.py index fedf9b5a..9e587beb 100755 --- a/uncompyle6/scanner.py +++ b/uncompyle6/scanner.py @@ -251,6 +251,8 @@ class Scanner(object): self.Token = tokenClass return self.Token +def op_has_argument(op, opc): + return op >= opc.HAVE_ARGUMENT def parse_fn_counts(argc): return ((argc & 0xFF), (argc >> 8) & 0xFF, (argc >> 16) & 0x7FFF) diff --git a/uncompyle6/scanners/scanner3.py b/uncompyle6/scanners/scanner3.py index fdbbd14a..d0034afb 100644 --- a/uncompyle6/scanners/scanner3.py +++ b/uncompyle6/scanners/scanner3.py @@ -25,6 +25,7 @@ from __future__ import print_function from collections import namedtuple from array import array +from uncompyle6.scanner import Scanner, op_has_argument from xdis.code import iscode from xdis.bytecode import Bytecode 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_TF = (POP_JUMP_IF_TRUE, POP_JUMP_IF_FALSE) -import uncompyle6.scanner as scan - -class Scanner3(scan.Scanner): +class Scanner3(Scanner): def __init__(self, version, show_asm=None, is_pypy=False): super(Scanner3, self).__init__(version, show_asm, is_pypy) @@ -234,7 +233,7 @@ class Scanner3(scan.Scanner): offset = inst.offset, linestart = inst.starts_line, op = op, - has_arg = (op >= op3.HAVE_ARGUMENT), + has_arg = op_has_argument(op, op3), opc = self.opc ) ) @@ -403,7 +402,7 @@ class Scanner3(scan.Scanner): # Determine structures and fix jumps in Python versions # since 2.3 - self.detect_structure(offset) + self.detect_structure(offset, targets) has_arg = (op >= op3.HAVE_ARGUMENT) if has_arg: @@ -517,7 +516,7 @@ class Scanner3(scan.Scanner): target += offset + 3 return target - def detect_structure(self, offset): + def detect_structure(self, offset, targets): """ Detect structures and their boundaries to fix optimized jumps in python2.3+ @@ -735,10 +734,33 @@ class Scanner3(scan.Scanner): self.structs.append({'type': 'if-then', 'start': start, 'end': rtarget}) + # It is important to distingish if this return is inside some sort + # except block return jump_prev = prev_op[offset] if self.is_pypy and code[jump_prev] == self.opc.COMPARE_OP: if self.opc.cmp_op[code[jump_prev+1]] == 'exception match': 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]) elif op in self.jump_if_pop: diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index 7703739b..35fdd689 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -739,11 +739,6 @@ class SourceWalker(GenericASTTraversal, object): if self.return_none or node != AST('return_stmt', [AST('ret_expr', [NONE]), Token('RETURN_VALUE')]): self.write(' ') 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.prune() # stop recursing