Fix up looping by reinstating JUMP_ABSOLUTE -> JUMP_BACK or CONTINUE

get jump offsets into jump attributes. Fix up 3.2 scanner paritally
and use that in 3.4 for in cross version disassembly.
This commit is contained in:
rocky
2015-12-26 03:06:03 -05:00
parent fe9c8d5734
commit 7a2703634f
13 changed files with 108 additions and 79 deletions

Binary file not shown.

View File

@@ -5,6 +5,14 @@ def handle(module):
module = exc
return module
def handle2(module):
if module == 'foo':
try:
module = 1
except ImportError as exc:
module = exc
return module
try:
pass
except ImportError as exc:

View File

@@ -0,0 +1,2 @@
while __name__ != '__main__':
b = 4

View File

@@ -1,2 +0,0 @@
while a:
b = c

View File

@@ -0,0 +1,5 @@
if __name__:
for i in (1,2):
x = 3
pass
pass

View File

@@ -405,15 +405,15 @@ class Python2Parser(PythonParser):
trystmt ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
try_middle COME_FROM
try_middle COME_FROM
# this is nested inside a trystmt
tryfinallystmt ::= SETUP_FINALLY suite_stmts
POP_BLOCK LOAD_CONST
COME_FROM suite_stmts_opt END_FINALLY
tryelsestmt ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
try_middle else_suite COME_FROM
tryelsestmt ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
try_middle else_suite COME_FROM
tryelsestmtc ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
try_middle else_suitec COME_FROM

View File

@@ -71,7 +71,7 @@ class Python3Parser(PythonParser):
_come_from ::= COME_FROM
_come_from ::=
list_for ::= expr FOR_ITER designator list_iter JUMP_ABSOLUTE
list_for ::= expr FOR_ITER designator list_iter JUMP_BACK
list_if ::= expr jmp_false list_iter
list_if_not ::= expr jmp_true list_iter
@@ -407,7 +407,7 @@ class Python3Parser(PythonParser):
testtrue ::= expr jmp_true
_ifstmts_jump ::= return_if_stmts
_ifstmts_jump ::= c_stmts_opt JUMP_FORWARD COME_FROM
_ifstmts_jump ::= c_stmts_opt JUMP_FORWARD COME_FROM _come_from
iflaststmt ::= testexpr c_stmts_opt JUMP_ABSOLUTE
@@ -459,7 +459,7 @@ class Python3Parser(PythonParser):
# This is used in Python 3 in
# "except ... as e" to remove 'e' after the c_stmts_opt finishes
except_suite_finalize ::= SETUP_FINALLY c_stmts_opt except_var_finalize
END_FINALLY JUMP_FORWARD
END_FINALLY _jump
except_var_finalize ::= POP_BLOCK POP_EXCEPT LOAD_CONST COME_FROM LOAD_CONST
designator del_stmt
@@ -490,7 +490,7 @@ class Python3Parser(PythonParser):
whilestmt ::= SETUP_LOOP
testexpr
l_stmts_opt JUMP_ABSOLUTE
l_stmts_opt JUMP_BACK
POP_BLOCK COME_FROM
whilestmt ::= SETUP_LOOP
@@ -515,11 +515,11 @@ class Python3Parser(PythonParser):
_for ::= GET_ITER FOR_ITER
_for ::= LOAD_CONST FOR_LOOP
for_block ::= l_stmts_opt JUMP_ABSOLUTE
for_block ::= l_stmts_opt JUMP_BACK
for_block ::= return_stmts _come_from
forstmt ::= SETUP_LOOP expr _for designator
for_block POP_BLOCK COME_FROM
for_block POP_BLOCK _come_from
forelsestmt ::= SETUP_LOOP expr _for designator
for_block POP_BLOCK else_suite COME_FROM

View File

@@ -34,11 +34,11 @@ class Scanner32(scan.Scanner):
tokens = self.tokenize(code_object)
return tokens
def disassemble(self, co):
def disassemble(self, co, classname=None):
"""
Convert code object <co> into a sequence of tokens.
Based on dis.disassemble() function.
The below is based on (an older version?) of Python dis.disassemble_bytes().
"""
# Container for tokens
tokens = []
@@ -47,9 +47,64 @@ class Scanner32(scan.Scanner):
codelen = len(code)
self.build_lines_data(co)
self.build_prev_op()
# Get jump targets
# Format: {target offset: [jump offsets]}
jump_targets = self.find_jump_targets()
# self.lines contains (block,addrLastInstr)
if classname:
classname = '_' + classname.lstrip('_') + '__'
def unmangle(name):
if name.startswith(classname) and name[-2:] != '__':
return name[len(classname) - 2:]
return name
# free = [ unmangle(name) for name in (co.co_cellvars + co.co_freevars) ]
# names = [ unmangle(name) for name in co.co_names ]
# varnames = [ unmangle(name) for name in co.co_varnames ]
else:
# free = co.co_cellvars + co.co_freevars
# names = co.co_names
# varnames = co.co_varnames
pass
# Scan for assertions. Later we will
# turn 'LOAD_GLOBAL' to 'LOAD_ASSERT' for those
# assertions
self.load_asserts = set()
for i in self.op_range(0, codelen):
if self.code[i] == POP_JUMP_IF_TRUE and self.code[i+3] == LOAD_GLOBAL:
if names[self.get_argument(i+3)] == 'AssertionError':
self.load_asserts.add(i+3)
# FIXME: reinstate code
# cf = self.find_jump_targets(self.code)
# # contains (code, [addrRefToCode])
# last_stmt = self.next_stmt[0]
# i = self.next_stmt[last_stmt]
# replace = {}
# while i < n-1:
# if self.lines[last_stmt].next > i:
# if self.code[last_stmt] == PRINT_ITEM:
# if self.code[i] == PRINT_ITEM:
# replace[i] = 'PRINT_ITEM_CONT'
# elif self.code[i] == PRINT_NEWLINE:
# replace[i] = 'PRINT_NEWLINE_CONT'
# last_stmt = i
# i = self.next_stmt[i]
# imports = self.all_instr(0, n, (IMPORT_NAME, IMPORT_FROM, IMPORT_STAR))
# if len(imports) > 1:
# last_import = imports[0]
# for i in imports[1:]:
# if self.lines[last_import].next > i:
# if self.code[last_import] == IMPORT_NAME == self.code[i]:
# replace[i] = 'IMPORT_NAME_CONT'
# last_import = i
# Initialize extended arg at 0. When extended arg op is encountered,
# variable preserved for next cycle and added as arg for next op
extended_arg = 0
@@ -63,6 +118,8 @@ class Scanner32(scan.Scanner):
offset='{}_{}'.format(offset, jump_idx)))
jump_idx += 1
op = code[offset]
op_name = opname[op]
# Create token and fill all the fields we can
# w/o touching arguments
current_token = Token(dis.opname[op])
@@ -97,6 +154,17 @@ class Scanner32(scan.Scanner):
if free is None:
free = co.co_cellvars + co.co_freevars
current_token.pattr = free[oparg]
if op == JUMP_ABSOLUTE:
current_token.pattr = current_token.attr = oparg
target = self.get_target(offset)
if target < offset:
if offset in self.stmts and self.code[offset+3] not in (END_FINALLY, POP_BLOCK) \
and offset not in self.not_continue:
op_name = 'CONTINUE'
else:
op_name = 'JUMP_BACK'
current_token.type = op_name
tokens.append(current_token)
return tokens, customize

View File

@@ -20,7 +20,7 @@ from collections import namedtuple
from array import array
from uncompyle6 import PYTHON_VERSION
from uncompyle6.scanner import Token, L65536
from uncompyle6.scanner import Token
import uncompyle6.opcodes.opcode_34
# Get all the opcodes into globals
@@ -145,6 +145,17 @@ class Scanner34(scan.Scanner):
if inst.opname != 'BUILD_SLICE':
customize[opname] = inst.argval
elif opname == 'JUMP_ABSOLUTE':
pattr = inst.argval
target = self.get_target(inst.offset)
if target < inst.offset:
if (inst.offset in self.stmts and
self.code[inst.offset+3] not in (END_FINALLY, POP_BLOCK)
and offset not in self.not_continue):
opname = 'CONTINUE'
else:
opname = 'JUMP_BACK'
elif inst.offset in self.load_asserts:
opname = 'LOAD_ASSERT'
@@ -160,71 +171,8 @@ class Scanner34(scan.Scanner):
pass
return tokens, {}
def disassemble_cross_version(self, co):
"""
Convert code object <co> into a sequence of tokens
FIXME: the below is not based on the current Python 3.4 dis.disassemble_bytes().
Fix that.
"""
# Container for tokens
tokens = []
customize = {}
self.code = code = array('B', co.co_code)
codelen = len(code)
self.build_lines_data(co)
self.build_prev_op()
# Get jump targets
# Format: {target offset: [jump offsets]}
jump_targets = self.find_jump_targets()
# Initialize extended arg at 0. When extended arg op is encountered,
# variable preserved for next cycle and added as arg for next op
extended_arg = 0
free = None
for offset in self.op_range(0, codelen):
# Add jump target tokens
if offset in jump_targets:
jump_idx = 0
for jump_offset in jump_targets[offset]:
tokens.append(Token('COME_FROM', None, repr(jump_offset),
offset='{}_{}'.format(offset, jump_idx)))
jump_idx += 1
op = code[offset]
# Create token and fill all the fields we can
# w/o touching arguments
current_token = Token(dis.opname[op])
current_token.offset = offset
if offset in self.linestarts:
current_token.linestart = self.linestarts[offset]
else:
current_token.linestart = None
if op >= dis.HAVE_ARGUMENT:
# Calculate op's argument value based on its argument and
# preceding extended argument, if any
oparg = code[offset+1] + code[offset+2]*256 + extended_arg
extended_arg = 0
if op == dis.EXTENDED_ARG:
extended_arg = oparg * L65536
# Fill token's attr/pattr fields
current_token.attr = oparg
if op in dis.hasconst:
current_token.pattr = repr(co.co_consts[oparg])
elif op in dis.hasname:
current_token.pattr = co.co_names[oparg]
elif op in dis.hasjrel:
current_token.pattr = repr(offset + 3 + oparg)
elif op in dis.haslocal:
current_token.pattr = co.co_varnames[oparg]
elif op in dis.hascompare:
current_token.pattr = dis.cmp_op[oparg]
elif op in dis.hasfree:
if free is None:
free = co.co_cellvars + co.co_freevars
current_token.pattr = free[oparg]
tokens.append(current_token)
return tokens, customize
def disassemble_cross_version(self, co, classname=None):
return scan.scanner32().disassemble(self, co, classname)
def build_lines_data(self, code_obj):
"""