Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(450)

Side by Side Diff: Lib/dis.py

Issue 11816: Add functions to return disassembly as string
Patch Set: Created 1 year, 8 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 """Disassembler of Python byte code into mnemonics.""" 1 """Disassembler of Python byte code into mnemonics."""
2 2
3 import sys 3 import sys
4 import types 4 import types
5 import collections
5 6
6 from opcode import * 7 from opcode import *
7 from opcode import __all__ as _opcodes_all 8 from opcode import __all__ as _opcodes_all
8 9
9 __all__ = ["code_info", "dis", "disassemble", "distb", "disco", 10 __all__ = ["code_info", "dis", "disassemble", "distb", "disco",
10 "findlinestarts", "findlabels", "show_code"] + _opcodes_all 11 "findlinestarts", "findlabels", "show_code"] + _opcodes_all
11 del _opcodes_all 12 del _opcodes_all
12 13
13 _have_code = (types.MethodType, types.FunctionType, types.CodeType, type) 14 _have_code = (types.MethodType, types.FunctionType, types.CodeType, type)
14 15
15 def _try_compile(source, name): 16 def _try_compile(source, name):
16 """Attempts to compile the given source, first as an expression and 17 """Attempts to compile the given source, first as an expression and
17 then as a statement if the first approach fails. 18 then as a statement if the first approach fails.
18 19
19 Utility function to accept strings in functions that otherwise 20 Utility function to accept strings in functions that otherwise
20 expect code objects 21 expect code objects
21 """ 22 """
22 try: 23 try:
23 c = compile(source, name, 'eval') 24 c = compile(source, name, 'eval')
24 except SyntaxError: 25 except SyntaxError:
25 c = compile(source, name, 'exec') 26 c = compile(source, name, 'exec')
26 return c 27 return c
27 28
28 def dis(x=None): 29 def dis(x=None, *, file=None):
29 """Disassemble classes, methods, functions, or code. 30 """Disassemble classes, methods, functions, or code.
30 31
31 With no argument, disassemble the last traceback. 32 With no argument, disassemble the last traceback.
eric.araujo 2011/10/06 15:03:51 Should document the meaning of the file argument.
32 33
33 """ 34 """
34 if x is None: 35 if x is None:
35 distb() 36 distb()
36 return 37 return
37 if hasattr(x, '__func__'): # Method 38 if hasattr(x, '__func__'): # Method
38 x = x.__func__ 39 x = x.__func__
39 if hasattr(x, '__code__'): # Function 40 if hasattr(x, '__code__'): # Function
40 x = x.__code__ 41 x = x.__code__
41 if hasattr(x, '__dict__'): # Class or module 42 if hasattr(x, '__dict__'): # Class or module
42 items = sorted(x.__dict__.items()) 43 items = sorted(x.__dict__.items())
43 for name, x1 in items: 44 for name, x1 in items:
44 if isinstance(x1, _have_code): 45 if isinstance(x1, _have_code):
45 print("Disassembly of %s:" % name) 46 print("Disassembly of %s:" % name, file=file)
46 try: 47 try:
47 dis(x1) 48 dis(x1)
48 except TypeError as msg: 49 except TypeError as msg:
49 print("Sorry:", msg) 50 print("Sorry:", msg, file=file)
50 print() 51 print(file=file)
51 elif hasattr(x, 'co_code'): # Code object 52 elif hasattr(x, 'co_code'): # Code object
52 disassemble(x) 53 disassemble(x, file=file)
53 elif isinstance(x, (bytes, bytearray)): # Raw bytecode 54 elif isinstance(x, (bytes, bytearray)): # Raw bytecode
54 _disassemble_bytes(x) 55 _disassemble_bytes(x, file=file)
55 elif isinstance(x, str): # Source code 56 elif isinstance(x, str): # Source code
56 _disassemble_str(x) 57 _disassemble_str(x, file=file)
57 else: 58 else:
58 raise TypeError("don't know how to disassemble %s objects" % 59 raise TypeError("don't know how to disassemble %s objects" %
59 type(x).__name__) 60 type(x).__name__)
60 61
61 def distb(tb=None): 62 def distb(tb=None, *, file=None):
62 """Disassemble a traceback (default: last traceback).""" 63 """Disassemble a traceback (default: last traceback)."""
63 if tb is None: 64 if tb is None:
64 try: 65 try:
65 tb = sys.last_traceback 66 tb = sys.last_traceback
66 except AttributeError: 67 except AttributeError:
67 raise RuntimeError("no last traceback to disassemble") 68 raise RuntimeError("no last traceback to disassemble")
68 while tb.tb_next: tb = tb.tb_next 69 while tb.tb_next: tb = tb.tb_next
69 disassemble(tb.tb_frame.f_code, tb.tb_lasti) 70 disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file)
70 71
71 # The inspect module interrogates this dictionary to build its 72 # The inspect module interrogates this dictionary to build its
72 # list of CO_* constants. It is also used by pretty_flags to 73 # list of CO_* constants. It is also used by pretty_flags to
73 # turn the co_flags field into a human readable list. 74 # turn the co_flags field into a human readable list.
74 COMPILER_FLAG_NAMES = { 75 COMPILER_FLAG_NAMES = {
75 1: "OPTIMIZED", 76 1: "OPTIMIZED",
76 2: "NEWLOCALS", 77 2: "NEWLOCALS",
77 4: "VARARGS", 78 4: "VARARGS",
78 8: "VARKEYWORDS", 79 8: "VARKEYWORDS",
79 16: "NESTED", 80 16: "NESTED",
80 32: "GENERATOR", 81 32: "GENERATOR",
81 64: "NOFREE", 82 64: "NOFREE",
82 } 83 }
83 84
84 def pretty_flags(flags): 85 def pretty_flags(flags):
85 """Return pretty representation of code flags.""" 86 """Return pretty representation of code flags."""
86 names = [] 87 names = []
87 for i in range(32): 88 for i in range(32):
88 flag = 1<<i 89 flag = 1<<i
89 if flags & flag: 90 if flags & flag:
90 names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag))) 91 names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag)))
91 flags ^= flag 92 flags ^= flag
92 if not flags: 93 if not flags:
93 break 94 break
94 else: 95 else:
95 names.append(hex(flags)) 96 names.append(hex(flags))
96 return ", ".join(names) 97 return ", ".join(names)
97 98
98 def code_info(x): 99 def _get_code_object(x):
99 """Formatted details of methods, functions, or code.""" 100 """Helper to handle methods, functions, strings and raw code objects"""
100 if hasattr(x, '__func__'): # Method 101 if hasattr(x, '__func__'): # Method
101 x = x.__func__ 102 x = x.__func__
102 if hasattr(x, '__code__'): # Function 103 if hasattr(x, '__code__'): # Function
103 x = x.__code__ 104 x = x.__code__
104 if isinstance(x, str): # Source code 105 if isinstance(x, str): # Source code
105 x = _try_compile(x, "<code_info>") 106 x = _try_compile(x, "<disassembly>")
106 if hasattr(x, 'co_code'): # Code object 107 if hasattr(x, 'co_code'): # Code object
107 return _format_code_info(x) 108 return x
108 else: 109 raise TypeError("don't know how to disassemble %s objects" %
109 raise TypeError("don't know how to disassemble %s objects" % 110 type(x).__name__)
110 type(x).__name__) 111
112 def code_info(x):
113 """Formatted details of methods, functions, or code."""
eric.araujo 2011/10/06 15:03:51 Can you improve this docstring so that it mentions
114 return _format_code_info(_get_code_object(x))
111 115
112 def _format_code_info(co): 116 def _format_code_info(co):
113 lines = [] 117 lines = []
114 lines.append("Name: %s" % co.co_name) 118 lines.append("Name: %s" % co.co_name)
115 lines.append("Filename: %s" % co.co_filename) 119 lines.append("Filename: %s" % co.co_filename)
116 lines.append("Argument count: %s" % co.co_argcount) 120 lines.append("Argument count: %s" % co.co_argcount)
117 lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount) 121 lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount)
118 lines.append("Number of locals: %s" % co.co_nlocals) 122 lines.append("Number of locals: %s" % co.co_nlocals)
119 lines.append("Stack size: %s" % co.co_stacksize) 123 lines.append("Stack size: %s" % co.co_stacksize)
120 lines.append("Flags: %s" % pretty_flags(co.co_flags)) 124 lines.append("Flags: %s" % pretty_flags(co.co_flags))
(...skipping 12 matching lines...) Expand all
133 if co.co_freevars: 137 if co.co_freevars:
134 lines.append("Free variables:") 138 lines.append("Free variables:")
135 for i_n in enumerate(co.co_freevars): 139 for i_n in enumerate(co.co_freevars):
136 lines.append("%4d: %s" % i_n) 140 lines.append("%4d: %s" % i_n)
137 if co.co_cellvars: 141 if co.co_cellvars:
138 lines.append("Cell variables:") 142 lines.append("Cell variables:")
139 for i_n in enumerate(co.co_cellvars): 143 for i_n in enumerate(co.co_cellvars):
140 lines.append("%4d: %s" % i_n) 144 lines.append("%4d: %s" % i_n)
141 return "\n".join(lines) 145 return "\n".join(lines)
142 146
143 def show_code(co): 147 def show_code(co, *, file=None):
144 """Print details of methods, functions, or code to stdout.""" 148 """Print details of methods, functions, or code to stdout."""
145 print(code_info(co)) 149 print(code_info(co), file=file)
146 150
147 def disassemble(co, lasti=-1): 151 _OpInfo = collections.namedtuple("_OpInfo",
148 """Disassemble a code object.""" 152 "opcode opname arg argval argrepr offset starts_line is_jump_target")
149 code = co.co_code 153
154 class OpInfo(_OpInfo):
155 """Details for a bytecode operation
156
157 Defined fields:
eric.araujo 2011/10/06 15:03:51 This block should be dedented once to the left.
158 opcode - numeric code for operation
159 opname - human readable name for operation
160 arg - numeric argument to operation (if any), otherwise None
161 argval - resolved arg value (if known), otherwise same as arg
162 argrepr - human readable description of operation argument
163 offset - start index of operation within bytecode sequence
164 starts_line - line started by this opcode (if any), otherwise None
165 is_jump_target - True if other code jumps to here, otherwise False
166 """
167
168 def get_opinfo(x):
169 """Iterator for the opcodes in methods, functions or code
170
171 Generates a series of OpInfo namedtuples giving the details of
172 each opcode in the supplied code.
173 """
174 co = _get_code_object(x)
175 cell_names = co.co_cellvars + co.co_freevars
176 linestarts = dict(findlinestarts(co))
177 return _get_opinfo_bytes(co.co_code, co.co_varnames, co.co_names,
178 co.co_consts, cell_names, linestarts)
179
180 def _get_arg_info(arg, info_source):
181 """Helper to get optional details about the operation argument
182
183 Returns the dereferenced argval and its repr() if the info
184 source is defined.
185 Otherwise return the arg and its repr().
186 """
187 argval = arg
188 if info_source is not None:
189 argval = info_source[arg]
190 if isinstance(argval, str):
191 details = argval
192 else:
193 details = repr(argval)
194 return argval, details
195
196
197 def _get_opinfo_bytes(code, varnames=None, names=None, constants=None,
198 cells=None, linestarts=None):
199 """Iterate over the opcodes in a bytecode string.
200
201 Generates a sequence of OpInfo namedtuples giving the details of each
202 opcode. Additional information about the code's runtime environment
203 (e.g. variable names, constants) can be specified using optional
204 arguments.
205
206 """
150 labels = findlabels(code) 207 labels = findlabels(code)
151 linestarts = dict(findlinestarts(co))
152 n = len(code)
153 i = 0
154 extended_arg = 0 208 extended_arg = 0
209 linestart = None
155 free = None 210 free = None
156 while i < n: 211 # enumerate() is not an option, since we sometimes process
157 op = code[i] 212 # multiple elements on a single pass through the loop
158 if i in linestarts:
159 if i > 0:
160 print()
161 print("%3d" % linestarts[i], end=' ')
162 else:
163 print(' ', end=' ')
164
165 if i == lasti: print('-->', end=' ')
166 else: print(' ', end=' ')
167 if i in labels: print('>>', end=' ')
168 else: print(' ', end=' ')
169 print(repr(i).rjust(4), end=' ')
170 print(opname[op].ljust(20), end=' ')
171 i = i+1
172 if op >= HAVE_ARGUMENT:
173 oparg = code[i] + code[i+1]*256 + extended_arg
174 extended_arg = 0
175 i = i+2
176 if op == EXTENDED_ARG:
177 extended_arg = oparg*65536
178 print(repr(oparg).rjust(5), end=' ')
179 if op in hasconst:
180 print('(' + repr(co.co_consts[oparg]) + ')', end=' ')
181 elif op in hasname:
182 print('(' + co.co_names[oparg] + ')', end=' ')
183 elif op in hasjrel:
184 print('(to ' + repr(i + oparg) + ')', end=' ')
185 elif op in haslocal:
186 print('(' + co.co_varnames[oparg] + ')', end=' ')
187 elif op in hascompare:
188 print('(' + cmp_op[oparg] + ')', end=' ')
189 elif op in hasfree:
190 if free is None:
191 free = co.co_cellvars + co.co_freevars
192 print('(' + free[oparg] + ')', end=' ')
193 print()
194
195 def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
196 constants=None):
197 labels = findlabels(code)
198 n = len(code) 213 n = len(code)
199 i = 0 214 i = 0
200 while i < n: 215 while i < n:
201 op = code[i] 216 op = code[i]
202 if i == lasti: print('-->', end=' ') 217 offset = i
203 else: print(' ', end=' ') 218 if linestarts is not None:
204 if i in labels: print('>>', end=' ') 219 linestart = linestarts.get(i, None)
205 else: print(' ', end=' ') 220 is_jump_target = i in labels
206 print(repr(i).rjust(4), end=' ')
207 print(opname[op].ljust(15), end=' ')
208 i = i+1 221 i = i+1
222 arg = None
223 argval = None
224 argrepr = ''
209 if op >= HAVE_ARGUMENT: 225 if op >= HAVE_ARGUMENT:
210 oparg = code[i] + code[i+1]*256 226 arg = code[i] + code[i+1]*256 + extended_arg
227 extended_arg = 0
211 i = i+2 228 i = i+2
212 print(repr(oparg).rjust(5), end=' ') 229 if op == EXTENDED_ARG:
230 extended_arg = arg*65536
231 # Set argval to the dereferenced value of the argument when
232 # availabe, and argrepr to the string representation of argval.
233 # _disassemble_bytes needs the string repr of the
234 # raw name index for LOAD_GLOBAL, LOAD_CONST, etc.
235 argval = arg
213 if op in hasconst: 236 if op in hasconst:
214 if constants: 237 argval, argrepr = _get_arg_info(arg, constants)
215 print('(' + repr(constants[oparg]) + ')', end=' ')
216 else:
217 print('(%d)'%oparg, end=' ')
218 elif op in hasname: 238 elif op in hasname:
219 if names is not None: 239 argval, argrepr = _get_arg_info(arg, names)
220 print('(' + names[oparg] + ')', end=' ')
221 else:
222 print('(%d)'%oparg, end=' ')
223 elif op in hasjrel: 240 elif op in hasjrel:
224 print('(to ' + repr(i + oparg) + ')', end=' ') 241 argval = i + arg
242 argrepr = "to " + repr(argval)
225 elif op in haslocal: 243 elif op in haslocal:
226 if varnames: 244 argval, argrepr = _get_arg_info(arg, varnames)
227 print('(' + varnames[oparg] + ')', end=' ')
228 else:
229 print('(%d)' % oparg, end=' ')
230 elif op in hascompare: 245 elif op in hascompare:
231 print('(' + cmp_op[oparg] + ')', end=' ') 246 argval = cmp_op[arg]
232 print() 247 argrepr = argval
248 elif op in hasfree:
249 argval, argrepr = _get_arg_info(arg, cells)
250 yield OpInfo(op, opname[op],
251 arg, argval, argrepr,
252 offset, linestart, is_jump_target)
233 253
234 def _disassemble_str(source): 254 def disassemble(co, lasti=-1, *, file=None):
255 """Disassemble a code object."""
256 cell_names = co.co_cellvars + co.co_freevars
257 linestarts = dict(findlinestarts(co))
258 _disassemble_bytes(co.co_code, lasti, co.co_varnames, co.co_names,
259 co.co_consts, cell_names, linestarts, file=file)
260
261 def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
262 constants=None, cells=None, linestarts=None,
263 *, file=None):
264 for opinfo in _get_opinfo_bytes(code, varnames, names,
265 constants, cells, linestarts):
266 # Column: Source code line number
267 # Omitted entirely if we have no line number info
268 if linestarts is not None:
269 if opinfo.starts_line is not None:
270 if opinfo.offset > 0:
271 print(file=file)
272 print("%3d" % opinfo.starts_line, end=' ', file=file)
273 else:
274 print(' ', end=' ', file=file)
275 # Column: Current instruction indicator
276 if opinfo.offset == lasti:
277 print('-->', end=' ', file=file)
278 else:
279 print(' ', end=' ', file=file)
280 # Column: Jump target marker
281 if opinfo.is_jump_target:
282 print('>>', end=' ', file=file)
283 else:
284 print(' ', end=' ', file=file)
285 # Column: Instruction offset from start of code sequence
286 print(repr(opinfo.offset).rjust(4), end=' ', file=file)
287 # Column: Opcode name
288 # XXX: _disassemble_bytes historically uses a smaller field
289 # Will standardise this once more of the stdlib tests have
290 # switched over to using get_opinfo rather than dis output
291 if linestarts is None:
292 print(opinfo.opname.ljust(15), end=' ', file=file)
293 else:
294 print(opinfo.opname.ljust(20), end=' ', file=file)
295 # Column: Opcode argument
296 if opinfo.arg is not None:
297 print(repr(opinfo.arg).rjust(5), end=' ', file=file)
298 # Column: Opcode argument details
299 if opinfo.argrepr:
300 print('(' + opinfo.argrepr + ')', end=' ', file=file)
301 print(file=file)
302
303 def _disassemble_str(source, *, file=None):
235 """Compile the source string, then disassemble the code object.""" 304 """Compile the source string, then disassemble the code object."""
236 disassemble(_try_compile(source, '<dis>')) 305 disassemble(_try_compile(source, '<dis>'), file=file)
237 306
238 disco = disassemble # XXX For backwards compatibility 307 disco = disassemble # XXX For backwards compatibility
239 308
240 def findlabels(code): 309 def findlabels(code):
241 """Detect all offsets in a byte code which are jump targets. 310 """Detect all offsets in a byte code which are jump targets.
242 311
243 Return the list of offsets. 312 Return the list of offsets.
244 313
245 """ 314 """
246 labels = [] 315 labels = []
316 # enumerate() is not an option, since we sometimes process
317 # multiple elements on a single pass through the loop
247 n = len(code) 318 n = len(code)
248 i = 0 319 i = 0
249 while i < n: 320 while i < n:
250 op = code[i] 321 op = code[i]
251 i = i+1 322 i = i+1
252 if op >= HAVE_ARGUMENT: 323 if op >= HAVE_ARGUMENT:
253 oparg = code[i] + code[i+1]*256 324 arg = code[i] + code[i+1]*256
254 i = i+2 325 i = i+2
255 label = -1 326 label = -1
256 if op in hasjrel: 327 if op in hasjrel:
257 label = i+oparg 328 label = i+arg
258 elif op in hasjabs: 329 elif op in hasjabs:
259 label = oparg 330 label = arg
260 if label >= 0: 331 if label >= 0:
261 if label not in labels: 332 if label not in labels:
262 labels.append(label) 333 labels.append(label)
263 return labels 334 return labels
264 335
265 def findlinestarts(code): 336 def findlinestarts(code):
266 """Find the offsets in a byte code which are start of lines in the source. 337 """Find the offsets in a byte code which are start of lines in the source.
267 338
268 Generate pairs (offset, lineno) as described in Python/compile.c. 339 Generate pairs (offset, lineno) as described in Python/compile.c.
269 340
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
302 source = f.read() 373 source = f.read()
303 if fn is not None: 374 if fn is not None:
304 f.close() 375 f.close()
305 else: 376 else:
306 fn = "<stdin>" 377 fn = "<stdin>"
307 code = compile(source, fn, "exec") 378 code = compile(source, fn, "exec")
308 dis(code) 379 dis(code)
309 380
310 if __name__ == "__main__": 381 if __name__ == "__main__":
311 _test() 382 _test()
OLDNEW

RSS Feeds Recent Issues | This issue
This is Rietveld cbc36f91f3f7