X Tutup
Skip to content

Commit a08ece3

Browse files
committed
pydisassemble improvements; DRY scannners
disas.py: - disassembles *all* code objects found scanner*.py: - no longer need to pass in version numbers; this is obtained from the class name - no longer pass in opcodes; this is done at initialization from the scanner name - all Pythoin 3 scanners support native disassembly
1 parent d42f84a commit a08ece3

File tree

15 files changed

+183
-124
lines changed

15 files changed

+183
-124
lines changed

pytest/test_load.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import sys
1+
import os, sys
22
from uncompyle6.load import load_file, check_object_path, load_module
33

44
def test_load():
55
"""Basic test of load_file, check_object_path and load_module"""
66
co = load_file(__file__)
77
obj_path = check_object_path(__file__)
8-
version, timestamp, magic_int, co2 = load_module(obj_path)
9-
assert sys.version[0:3] == str(version)
10-
assert co == co2
8+
if os.path.exists(obj_path):
9+
version, timestamp, magic_int, co2 = load_module(obj_path)
10+
assert sys.version[0:3] == str(version)
11+
assert co == co2
12+
else:
13+
assert True, "Skipped because we can't find %s" % obj_path

pytest/testdata/if-2.7.right

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@
99
12 JUMP_FORWARD 0 '15'
1010
15 LOAD_CONST 0 ''
1111
18 RETURN_VALUE ''
12-

pytest/testdata/ifelse-2.7.right

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,3 @@
1212
18 STORE_NAME 2 'd'
1313
21 LOAD_CONST 2 ''
1414
24 RETURN_VALUE ''
15-

uncompyle6/disas.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from __future__ import print_function
2020

2121
import os, sys
22+
from collections import deque
2223

2324
import uncompyle6
2425
from uncompyle6.code import iscode
@@ -40,17 +41,29 @@ def disco(version, co, out=None, use_uncompyle6_format=False):
4041
file=real_out)
4142

4243
scanner = get_scanner(version)
43-
if (not use_uncompyle6_format) and hasattr(scanner, 'disassemble_native'):
44-
tokens, customize = scanner.disassemble_native(co, True)
45-
else:
46-
tokens, customize = scanner.disassemble(co)
47-
48-
# FIXME: This should go in another routine and
49-
# a queue should be kept of code to disassemble.
50-
for t in tokens:
51-
print(t.format(), file=real_out)
52-
print(file=out)
5344

45+
disasm = scanner.disassemble_native \
46+
if (not use_uncompyle6_format) and hasattr(scanner, 'disassemble_native') \
47+
else scanner.disassemble
48+
49+
queue = deque([co])
50+
disco_loop(disasm, queue, real_out, use_uncompyle6_format)
51+
52+
53+
def disco_loop(disasm, queue, real_out, use_uncompyle6_format):
54+
while len(queue) > 0:
55+
co = queue.popleft()
56+
if co.co_name != '<module>':
57+
print('\n# %s line %d of %s' %
58+
(co.co_name, co.co_firstlineno, co.co_filename),
59+
file=real_out)
60+
tokens, customize = disasm(co, use_uncompyle6_format)
61+
for t in tokens:
62+
if iscode(t.pattr):
63+
queue.append(t.pattr)
64+
print(t.format(), file=real_out)
65+
pass
66+
pass
5467

5568
def disassemble_file(filename, outstream=None, native=False):
5669
"""

uncompyle6/scanner.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def __init__(self, co, scanner, classname=None):
5050
class Scanner(object):
5151

5252
def __init__(self, version):
53+
self.version = version
5354
# FIXME: DRY
5455
if version == 2.7:
5556
self.opc = opcode_27
@@ -68,6 +69,7 @@ def __init__(self, version):
6869
else:
6970
raise TypeError("%s is not a Python version I know about" % version)
7071

72+
self.opname = self.opc.opname
7173
# FIXME: This weird Python2 behavior is not Python3
7274
self.resetTokenClass()
7375

@@ -96,9 +98,9 @@ def print_bytecode(self):
9698
op = self.code[i]
9799
if op in self.opc.hasjabs+self.opc.hasjrel:
98100
dest = self.get_target(i, op)
99-
print('%i\t%s\t%i' % (i, self.opc.opname[op], dest))
101+
print('%i\t%s\t%i' % (i, self.opname[op], dest))
100102
else:
101-
print('%i\t%s\t' % (i, self.opc.opname[op]))
103+
print('%i\t%s\t' % (i, self.opname[op]))
102104

103105
def first_instr(self, start, end, instr, target=None, exact=True):
104106
"""
@@ -291,27 +293,28 @@ def get_scanner(version):
291293
# from trepan.api import debug;
292294
# debug(start_opts={'startup-profile': True})
293295

296+
# FIXME: see if we can do better
294297
if version == 2.7:
295298
import uncompyle6.scanners.scanner27 as scan
296-
scanner = scan.Scanner27(version)
299+
scanner = scan.Scanner27()
297300
elif version == 2.6:
298301
import uncompyle6.scanners.scanner26 as scan
299-
scanner = scan.Scanner26(version)
302+
scanner = scan.Scanner26()
300303
elif version == 2.5:
301304
import uncompyle6.scanners.scanner25 as scan
302-
scanner = scan.Scanner25(version)
305+
scanner = scan.Scanner25()
303306
elif version == 3.2:
304307
import uncompyle6.scanners.scanner32 as scan
305-
scanner = scan.Scanner32(version)
308+
scanner = scan.Scanner32()
306309
elif version == 3.3:
307310
import uncompyle6.scanners.scanner33 as scan
308-
scanner = scan.Scanner33(version)
311+
scanner = scan.Scanner33()
309312
elif version == 3.4:
310313
import uncompyle6.scanners.scanner34 as scan
311-
scanner = scan.Scanner34(version)
314+
scanner = scan.Scanner34()
312315
elif version == 3.5:
313316
import uncompyle6.scanners.scanner35 as scan
314-
scanner = scan.Scanner35(version)
317+
scanner = scan.Scanner35()
315318
else:
316319
raise RuntimeError("Unsupported Python version %d" % version)
317320
return scanner

uncompyle6/scanners/scanner25.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
2-
# Copyright (c) 1999 John Aycock
3-
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
1+
# Copyright (c) 2015-2016 by Rocky Bernstein
42
# Copyright (c) 2005 by Dan Pascu <dan@windowmaker.org>
5-
# Copyright (c) 2015 by Rocky Bernstein
3+
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
4+
# Copyright (c) 1999 John Aycock
65
#
76
"""
87
Python 2.5 bytecode scanner/deparser
@@ -20,8 +19,8 @@
2019
import uncompyle6.scanner as scan
2120

2221
class Scanner25(scan.Scanner):
23-
def __init__(self, version):
24-
scan.Scanner.__init__(self, 2.5) # check
22+
def __init__(self):
23+
scan.Scanner.__init__(self, 2.5)
2524

2625
def disassemble(self, co, classname=None, code_objects={}):
2726
'''
@@ -132,7 +131,7 @@ def unmangle(name):
132131
extended_arg = 0
133132
for offset in self.op_range(0, codelen):
134133
op = self.code[offset]
135-
op_name = opname[op]
134+
op_name = self.opname[op]
136135
oparg = None; pattr = None
137136

138137
if offset in cf:
@@ -913,10 +912,4 @@ def find_jump_targets(self, code):
913912
label = self.fixed_jumps[i]
914913
targets[label] = targets.get(label, []) + [i]
915914
return targets
916-
917-
if __name__ == "__main__":
918-
import inspect
919-
co = inspect.currentframe().f_code
920-
tokens, customize = Scanner25().disassemble(co)
921-
for t in tokens:
922-
print(t)
915+
pass

uncompyle6/scanners/scanner26.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import uncompyle6.scanner as scan
2020

2121
class Scanner26(scan.Scanner):
22-
def __init__(self, version):
22+
def __init__(self):
2323
scan.Scanner.__init__(self, 2.6)
2424

2525
def disassemble(self, co, classname=None, code_objects={}):
@@ -130,7 +130,7 @@ def unmangle(name):
130130
extended_arg = 0
131131
for offset in self.op_range(0, codelen):
132132
op = self.code[offset]
133-
op_name = opname[op]
133+
op_name = self.opname[op]
134134
oparg = None; pattr = None
135135

136136
if offset in cf:
@@ -180,7 +180,10 @@ def unmangle(name):
180180
elif op in haslocal:
181181
pattr = varnames[oparg]
182182
elif op in hascompare:
183-
pattr = cmp_op[oparg]
183+
try:
184+
pattr = cmp_op[oparg]
185+
except:
186+
from trepan.api import debug; debug()
184187
elif op in hasfree:
185188
pattr = free[oparg]
186189
if offset in self.toChange:
@@ -917,8 +920,13 @@ def find_jump_targets(self, code):
917920
return targets
918921

919922
if __name__ == "__main__":
920-
import inspect
921-
co = inspect.currentframe().f_code
922-
tokens, customize = Scanner26().disassemble(co)
923-
for t in tokens:
924-
print(t)
923+
from uncompyle6 import PYTHON_VERSION
924+
if PYTHON_VERSION == 2.6:
925+
import inspect
926+
co = inspect.currentframe().f_code
927+
tokens, customize = Scanner26().disassemble(co)
928+
for t in tokens:
929+
print(t.format())
930+
else:
931+
print("Need to be Python 2.6 to demo; I am %s." %
932+
PYTHON_VERSION)

uncompyle6/scanners/scanner27.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
import uncompyle6.scanner as scan
2323

2424
class Scanner27(scan.Scanner):
25-
def __init__(self, version):
26-
scan.Scanner.__init__(self, 2.7) # check
25+
def __init__(self):
26+
scan.Scanner.__init__(self, 2.7)
2727

2828
def disassemble(self, co, classname=None, code_objects={}):
2929
"""
@@ -192,7 +192,7 @@ def unmangle(name):
192192
tokens.append(Token(replace[offset], oparg, pattr, offset, linestart))
193193
return tokens, customize
194194

195-
def disassemble_native(self, co, opnames, classname=None, code_objects={}):
195+
def disassemble_native(self, co, classname=None, code_objects={}):
196196
"""
197197
Like disassemble3 but doesn't try to adjust any opcodes.
198198
"""
@@ -728,8 +728,7 @@ def find_jump_targets(self):
728728

729729
if __name__ == "__main__":
730730
co = inspect.currentframe().f_code
731-
from uncompyle6 import PYTHON_VERSION
732-
tokens, customize = Scanner27(PYTHON_VERSION).disassemble(co)
731+
tokens, customize = Scanner27().disassemble(co)
733732
for t in tokens:
734733
print(t)
735734
pass

uncompyle6/scanners/scanner3.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,15 @@
4545

4646
class Scanner3(scan.Scanner):
4747

48-
## FIXME opnames should be passed in here
4948
def __init__(self, version):
50-
self.version = version
51-
self.opnames = {} # will eventually get passed in
52-
scan.Scanner.__init__(self, version)
53-
49+
if PYTHON3:
50+
super().__init__(version)
51+
else:
52+
super(Scanner3, self).__init__(version)
5453

55-
## FIXME opnames should be moved to init
56-
def disassemble3(self, co, opnames, classname=None, code_objects={}):
54+
def disassemble3(self, co, classname=None, code_objects={}):
5755
"""
58-
Disassemble a Python 3 ode object, returning a list of 'Token'.
56+
Disassemble a Python 3 code object, returning a list of 'Token'.
5957
Various tranformations are made to assist the deparsing grammar.
6058
For example:
6159
- various types of LOAD_CONST's are categorized in terms of what they load
@@ -65,8 +63,6 @@ def disassemble3(self, co, opnames, classname=None, code_objects={}):
6563
dis.disassemble().
6664
"""
6765

68-
self.opnames = opnames # will eventually disasppear
69-
7066
# import dis; dis.disassemble(co) # DEBUG
7167

7268
# Container for tokens
@@ -76,7 +72,7 @@ def disassemble3(self, co, opnames, classname=None, code_objects={}):
7672
self.build_lines_data(co)
7773
self.build_prev_op()
7874

79-
bytecode = dis3.Bytecode(co, opnames)
75+
bytecode = dis3.Bytecode(co, self.opname)
8076

8177
# Scan for assertions. Later we will
8278
# turn 'LOAD_GLOBAL' to 'LOAD_ASSERT' for those
@@ -164,7 +160,7 @@ def disassemble3(self, co, opnames, classname=None, code_objects={}):
164160
pattr = inst.argval
165161
target = self.get_target(inst.offset)
166162
if target < inst.offset:
167-
next_opname = opnames[self.code[inst.offset+3]]
163+
next_opname = self.opname[self.code[inst.offset+3]]
168164
if (inst.offset in self.stmts and
169165
next_opname not in ('END_FINALLY', 'POP_BLOCK')
170166
and inst.offset not in self.not_continue):
@@ -187,7 +183,7 @@ def disassemble3(self, co, opnames, classname=None, code_objects={}):
187183
pass
188184
return tokens, {}
189185

190-
def disassemble3_native(self, co, opnames, classname=None, code_objects={}):
186+
def disassemble3_native(self, co, classname=None, code_objects={}):
191187
"""
192188
Like disassemble3 but doesn't try to adjust any opcodes.
193189
"""
@@ -196,7 +192,7 @@ def disassemble3_native(self, co, opnames, classname=None, code_objects={}):
196192

197193
self.code = array('B', co.co_code)
198194

199-
bytecode = dis3.Bytecode(co, opnames)
195+
bytecode = dis3.Bytecode(co, self.opname)
200196

201197
for inst in bytecode:
202198
pattr = inst.argrepr
@@ -292,7 +288,7 @@ def unmangle(name):
292288
pass
293289

294290
op = code[offset]
295-
op_name = op3.opname[op]
291+
op_name = self.opname[op]
296292

297293
oparg = None; pattr = None
298294

@@ -907,11 +903,15 @@ def remove_mid_line_ifs(self, ifs):
907903
return filtered
908904

909905
if __name__ == "__main__":
910-
import inspect
911-
co = inspect.currentframe().f_code
912906
from uncompyle6 import PYTHON_VERSION
913-
from opcode import opname
914-
tokens, customize = Scanner3(PYTHON_VERSION).disassemble3(co, opname)
915-
for t in tokens:
916-
print(t)
907+
if PYTHON_VERSION >= 3.2:
908+
import inspect
909+
co = inspect.currentframe().f_code
910+
from uncompyle6 import PYTHON_VERSION
911+
tokens, customize = Scanner3(PYTHON_VERSION).disassemble3(co)
912+
for t in tokens:
913+
print(t.format())
914+
else:
915+
print("Need to be Python 3.2 or greater to demo; I am %s." %
916+
PYTHON_VERSION)
917917
pass

0 commit comments

Comments
 (0)
X Tutup