1 """Disassembler of Python byte code into mnemonics."""
2
3 import sys
4 import types
5 import collections
6 import io
7
8 from opcode import *
9 from opcode import (
10 __all__ as _opcodes_all,
11 _cache_format,
12 _inline_cache_entries,
13 _nb_ops,
14 _specializations,
15 _specialized_instructions,
16 )
17
18 __all__ = ["code_info", "dis", "disassemble", "distb", "disco",
19 "findlinestarts", "findlabels", "show_code",
20 "get_instructions", "Instruction", "Bytecode"] + _opcodes_all
21 del _opcodes_all
22
23 _have_code = (types.MethodType, types.FunctionType, types.CodeType,
24 classmethod, staticmethod, type)
25
26 FORMAT_VALUE = opmap['FORMAT_VALUE']
27 FORMAT_VALUE_CONVERTERS = (
28 (None, ''),
29 (str, 'str'),
30 (repr, 'repr'),
31 (ascii, 'ascii'),
32 )
33 MAKE_FUNCTION = opmap['MAKE_FUNCTION']
34 MAKE_FUNCTION_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure')
35
36 LOAD_CONST = opmap['LOAD_CONST']
37 LOAD_GLOBAL = opmap['LOAD_GLOBAL']
38 BINARY_OP = opmap['BINARY_OP']
39 JUMP_BACKWARD = opmap['JUMP_BACKWARD']
40
41 CACHE = opmap["CACHE"]
42
43 _all_opname = list(opname)
44 _all_opmap = dict(opmap)
45 _empty_slot = [slot for slot, name in enumerate(_all_opname) if name.startswith("<")]
46 for spec_op, specialized in zip(_empty_slot, _specialized_instructions):
47 # fill opname and opmap
48 _all_opname[spec_op] = specialized
49 _all_opmap[specialized] = spec_op
50
51 deoptmap = {
52 specialized: base for base, family in _specializations.items() for specialized in family
53 }
54
55 def _try_compile(source, name):
56 """Attempts to compile the given source, first as an expression and
57 then as a statement if the first approach fails.
58
59 Utility function to accept strings in functions that otherwise
60 expect code objects
61 """
62 try:
63 c = compile(source, name, 'eval')
64 except SyntaxError:
65 c = compile(source, name, 'exec')
66 return c
67
68 def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False):
69 """Disassemble classes, methods, functions, and other compiled objects.
70
71 With no argument, disassemble the last traceback.
72
73 Compiled objects currently include generator objects, async generator
74 objects, and coroutine objects, all of which store their code object
75 in a special attribute.
76 """
77 if x is None:
78 distb(file=file, show_caches=show_caches, adaptive=adaptive)
79 return
80 # Extract functions from methods.
81 if hasattr(x, '__func__'):
82 x = x.__func__
83 # Extract compiled code objects from...
84 if hasattr(x, '__code__'): # ...a function, or
85 x = x.__code__
86 elif hasattr(x, 'gi_code'): #...a generator object, or
87 x = x.gi_code
88 elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or
89 x = x.ag_code
90 elif hasattr(x, 'cr_code'): #...a coroutine.
91 x = x.cr_code
92 # Perform the disassembly.
93 if hasattr(x, '__dict__'): # Class or module
94 items = sorted(x.__dict__.items())
95 for name, x1 in items:
96 if isinstance(x1, _have_code):
97 print("Disassembly of %s:" % name, file=file)
98 try:
99 dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
100 except TypeError as msg:
101 print("Sorry:", msg, file=file)
102 print(file=file)
103 elif hasattr(x, 'co_code'): # Code object
104 _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
105 elif isinstance(x, (bytes, bytearray)): # Raw bytecode
106 _disassemble_bytes(x, file=file, show_caches=show_caches)
107 elif isinstance(x, str): # Source code
108 _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
109 else:
110 raise TypeError("don't know how to disassemble %s objects" %
111 type(x).__name__)
112
113 def distb(tb=None, *, file=None, show_caches=False, adaptive=False):
114 """Disassemble a traceback (default: last traceback)."""
115 if tb is None:
116 try:
117 tb = sys.last_traceback
118 except AttributeError:
119 raise RuntimeError("no last traceback to disassemble") from None
120 while tb.tb_next: tb = tb.tb_next
121 disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive)
122
123 # The inspect module interrogates this dictionary to build its
124 # list of CO_* constants. It is also used by pretty_flags to
125 # turn the co_flags field into a human readable list.
126 COMPILER_FLAG_NAMES = {
127 1: "OPTIMIZED",
128 2: "NEWLOCALS",
129 4: "VARARGS",
130 8: "VARKEYWORDS",
131 16: "NESTED",
132 32: "GENERATOR",
133 64: "NOFREE",
134 128: "COROUTINE",
135 256: "ITERABLE_COROUTINE",
136 512: "ASYNC_GENERATOR",
137 }
138
139 def pretty_flags(flags):
140 """Return pretty representation of code flags."""
141 names = []
142 for i in range(32):
143 flag = 1<<i
144 if flags & flag:
145 names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag)))
146 flags ^= flag
147 if not flags:
148 break
149 else:
150 names.append(hex(flags))
151 return ", ".join(names)
152
153 class ESC[4;38;5;81m_Unknown:
154 def __repr__(self):
155 return "<unknown>"
156
157 # Sentinel to represent values that cannot be calculated
158 UNKNOWN = _Unknown()
159
160 def _get_code_object(x):
161 """Helper to handle methods, compiled or raw code objects, and strings."""
162 # Extract functions from methods.
163 if hasattr(x, '__func__'):
164 x = x.__func__
165 # Extract compiled code objects from...
166 if hasattr(x, '__code__'): # ...a function, or
167 x = x.__code__
168 elif hasattr(x, 'gi_code'): #...a generator object, or
169 x = x.gi_code
170 elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or
171 x = x.ag_code
172 elif hasattr(x, 'cr_code'): #...a coroutine.
173 x = x.cr_code
174 # Handle source code.
175 if isinstance(x, str):
176 x = _try_compile(x, "<disassembly>")
177 # By now, if we don't have a code object, we can't disassemble x.
178 if hasattr(x, 'co_code'):
179 return x
180 raise TypeError("don't know how to disassemble %s objects" %
181 type(x).__name__)
182
183 def _deoptop(op):
184 name = _all_opname[op]
185 return _all_opmap[deoptmap[name]] if name in deoptmap else op
186
187 def _get_code_array(co, adaptive):
188 return co._co_code_adaptive if adaptive else co.co_code
189
190 def code_info(x):
191 """Formatted details of methods, functions, or code."""
192 return _format_code_info(_get_code_object(x))
193
194 def _format_code_info(co):
195 lines = []
196 lines.append("Name: %s" % co.co_name)
197 lines.append("Filename: %s" % co.co_filename)
198 lines.append("Argument count: %s" % co.co_argcount)
199 lines.append("Positional-only arguments: %s" % co.co_posonlyargcount)
200 lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount)
201 lines.append("Number of locals: %s" % co.co_nlocals)
202 lines.append("Stack size: %s" % co.co_stacksize)
203 lines.append("Flags: %s" % pretty_flags(co.co_flags))
204 if co.co_consts:
205 lines.append("Constants:")
206 for i_c in enumerate(co.co_consts):
207 lines.append("%4d: %r" % i_c)
208 if co.co_names:
209 lines.append("Names:")
210 for i_n in enumerate(co.co_names):
211 lines.append("%4d: %s" % i_n)
212 if co.co_varnames:
213 lines.append("Variable names:")
214 for i_n in enumerate(co.co_varnames):
215 lines.append("%4d: %s" % i_n)
216 if co.co_freevars:
217 lines.append("Free variables:")
218 for i_n in enumerate(co.co_freevars):
219 lines.append("%4d: %s" % i_n)
220 if co.co_cellvars:
221 lines.append("Cell variables:")
222 for i_n in enumerate(co.co_cellvars):
223 lines.append("%4d: %s" % i_n)
224 return "\n".join(lines)
225
226 def show_code(co, *, file=None):
227 """Print details of methods, functions, or code to *file*.
228
229 If *file* is not provided, the output is printed on stdout.
230 """
231 print(code_info(co), file=file)
232
233 Positions = collections.namedtuple(
234 'Positions',
235 [
236 'lineno',
237 'end_lineno',
238 'col_offset',
239 'end_col_offset',
240 ],
241 defaults=[None] * 4
242 )
243
244 _Instruction = collections.namedtuple(
245 "_Instruction",
246 [
247 'opname',
248 'opcode',
249 'arg',
250 'argval',
251 'argrepr',
252 'offset',
253 'starts_line',
254 'is_jump_target',
255 'positions'
256 ],
257 defaults=[None]
258 )
259
260 _Instruction.opname.__doc__ = "Human readable name for operation"
261 _Instruction.opcode.__doc__ = "Numeric code for operation"
262 _Instruction.arg.__doc__ = "Numeric argument to operation (if any), otherwise None"
263 _Instruction.argval.__doc__ = "Resolved arg value (if known), otherwise same as arg"
264 _Instruction.argrepr.__doc__ = "Human readable description of operation argument"
265 _Instruction.offset.__doc__ = "Start index of operation within bytecode sequence"
266 _Instruction.starts_line.__doc__ = "Line started by this opcode (if any), otherwise None"
267 _Instruction.is_jump_target.__doc__ = "True if other code jumps to here, otherwise False"
268 _Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction"
269
270 _ExceptionTableEntry = collections.namedtuple("_ExceptionTableEntry",
271 "start end target depth lasti")
272
273 _OPNAME_WIDTH = 20
274 _OPARG_WIDTH = 5
275
276 class ESC[4;38;5;81mInstruction(ESC[4;38;5;149m_Instruction):
277 """Details for a bytecode operation
278
279 Defined fields:
280 opname - human readable name for operation
281 opcode - numeric code for operation
282 arg - numeric argument to operation (if any), otherwise None
283 argval - resolved arg value (if known), otherwise same as arg
284 argrepr - human readable description of operation argument
285 offset - start index of operation within bytecode sequence
286 starts_line - line started by this opcode (if any), otherwise None
287 is_jump_target - True if other code jumps to here, otherwise False
288 positions - Optional dis.Positions object holding the span of source code
289 covered by this instruction
290 """
291
292 def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4):
293 """Format instruction details for inclusion in disassembly output
294
295 *lineno_width* sets the width of the line number field (0 omits it)
296 *mark_as_current* inserts a '-->' marker arrow as part of the line
297 *offset_width* sets the width of the instruction offset field
298 """
299 fields = []
300 # Column: Source code line number
301 if lineno_width:
302 if self.starts_line is not None:
303 lineno_fmt = "%%%dd" % lineno_width
304 fields.append(lineno_fmt % self.starts_line)
305 else:
306 fields.append(' ' * lineno_width)
307 # Column: Current instruction indicator
308 if mark_as_current:
309 fields.append('-->')
310 else:
311 fields.append(' ')
312 # Column: Jump target marker
313 if self.is_jump_target:
314 fields.append('>>')
315 else:
316 fields.append(' ')
317 # Column: Instruction offset from start of code sequence
318 fields.append(repr(self.offset).rjust(offset_width))
319 # Column: Opcode name
320 fields.append(self.opname.ljust(_OPNAME_WIDTH))
321 # Column: Opcode argument
322 if self.arg is not None:
323 fields.append(repr(self.arg).rjust(_OPARG_WIDTH))
324 # Column: Opcode argument details
325 if self.argrepr:
326 fields.append('(' + self.argrepr + ')')
327 return ' '.join(fields).rstrip()
328
329
330 def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False):
331 """Iterator for the opcodes in methods, functions or code
332
333 Generates a series of Instruction named tuples giving the details of
334 each operations in the supplied code.
335
336 If *first_line* is not None, it indicates the line number that should
337 be reported for the first source line in the disassembled code.
338 Otherwise, the source line information (if any) is taken directly from
339 the disassembled code object.
340 """
341 co = _get_code_object(x)
342 linestarts = dict(findlinestarts(co))
343 if first_line is not None:
344 line_offset = first_line - co.co_firstlineno
345 else:
346 line_offset = 0
347 return _get_instructions_bytes(_get_code_array(co, adaptive),
348 co._varname_from_oparg,
349 co.co_names, co.co_consts,
350 linestarts, line_offset,
351 co_positions=co.co_positions(),
352 show_caches=show_caches)
353
354 def _get_const_value(op, arg, co_consts):
355 """Helper to get the value of the const in a hasconst op.
356
357 Returns the dereferenced constant if this is possible.
358 Otherwise (if it is a LOAD_CONST and co_consts is not
359 provided) returns the dis.UNKNOWN sentinel.
360 """
361 assert op in hasconst
362
363 argval = UNKNOWN
364 if op == LOAD_CONST:
365 if co_consts is not None:
366 argval = co_consts[arg]
367 return argval
368
369 def _get_const_info(op, arg, co_consts):
370 """Helper to get optional details about const references
371
372 Returns the dereferenced constant and its repr if the value
373 can be calculated.
374 Otherwise returns the sentinel value dis.UNKNOWN for the value
375 and an empty string for its repr.
376 """
377 argval = _get_const_value(op, arg, co_consts)
378 argrepr = repr(argval) if argval is not UNKNOWN else ''
379 return argval, argrepr
380
381 def _get_name_info(name_index, get_name, **extrainfo):
382 """Helper to get optional details about named references
383
384 Returns the dereferenced name as both value and repr if the name
385 list is defined.
386 Otherwise returns the sentinel value dis.UNKNOWN for the value
387 and an empty string for its repr.
388 """
389 if get_name is not None:
390 argval = get_name(name_index, **extrainfo)
391 return argval, argval
392 else:
393 return UNKNOWN, ''
394
395 def _parse_varint(iterator):
396 b = next(iterator)
397 val = b & 63
398 while b&64:
399 val <<= 6
400 b = next(iterator)
401 val |= b&63
402 return val
403
404 def _parse_exception_table(code):
405 iterator = iter(code.co_exceptiontable)
406 entries = []
407 try:
408 while True:
409 start = _parse_varint(iterator)*2
410 length = _parse_varint(iterator)*2
411 end = start + length
412 target = _parse_varint(iterator)*2
413 dl = _parse_varint(iterator)
414 depth = dl >> 1
415 lasti = bool(dl&1)
416 entries.append(_ExceptionTableEntry(start, end, target, depth, lasti))
417 except StopIteration:
418 return entries
419
420 def _is_backward_jump(op):
421 return 'JUMP_BACKWARD' in opname[op]
422
423 def _get_instructions_bytes(code, varname_from_oparg=None,
424 names=None, co_consts=None,
425 linestarts=None, line_offset=0,
426 exception_entries=(), co_positions=None,
427 show_caches=False):
428 """Iterate over the instructions in a bytecode string.
429
430 Generates a sequence of Instruction namedtuples giving the details of each
431 opcode. Additional information about the code's runtime environment
432 (e.g. variable names, co_consts) can be specified using optional
433 arguments.
434
435 """
436 co_positions = co_positions or iter(())
437 get_name = None if names is None else names.__getitem__
438 labels = set(findlabels(code))
439 for start, end, target, _, _ in exception_entries:
440 for i in range(start, end):
441 labels.add(target)
442 starts_line = None
443 for offset, op, arg in _unpack_opargs(code):
444 if linestarts is not None:
445 starts_line = linestarts.get(offset, None)
446 if starts_line is not None:
447 starts_line += line_offset
448 is_jump_target = offset in labels
449 argval = None
450 argrepr = ''
451 positions = Positions(*next(co_positions, ()))
452 deop = _deoptop(op)
453 if arg is not None:
454 # Set argval to the dereferenced value of the argument when
455 # available, and argrepr to the string representation of argval.
456 # _disassemble_bytes needs the string repr of the
457 # raw name index for LOAD_GLOBAL, LOAD_CONST, etc.
458 argval = arg
459 if deop in hasconst:
460 argval, argrepr = _get_const_info(deop, arg, co_consts)
461 elif deop in hasname:
462 if deop == LOAD_GLOBAL:
463 argval, argrepr = _get_name_info(arg//2, get_name)
464 if (arg & 1) and argrepr:
465 argrepr = "NULL + " + argrepr
466 else:
467 argval, argrepr = _get_name_info(arg, get_name)
468 elif deop in hasjabs:
469 argval = arg*2
470 argrepr = "to " + repr(argval)
471 elif deop in hasjrel:
472 signed_arg = -arg if _is_backward_jump(deop) else arg
473 argval = offset + 2 + signed_arg*2
474 argrepr = "to " + repr(argval)
475 elif deop in haslocal or deop in hasfree:
476 argval, argrepr = _get_name_info(arg, varname_from_oparg)
477 elif deop in hascompare:
478 argval = cmp_op[arg]
479 argrepr = argval
480 elif deop == FORMAT_VALUE:
481 argval, argrepr = FORMAT_VALUE_CONVERTERS[arg & 0x3]
482 argval = (argval, bool(arg & 0x4))
483 if argval[1]:
484 if argrepr:
485 argrepr += ', '
486 argrepr += 'with format'
487 elif deop == MAKE_FUNCTION:
488 argrepr = ', '.join(s for i, s in enumerate(MAKE_FUNCTION_FLAGS)
489 if arg & (1<<i))
490 elif deop == BINARY_OP:
491 _, argrepr = _nb_ops[arg]
492 yield Instruction(_all_opname[op], op,
493 arg, argval, argrepr,
494 offset, starts_line, is_jump_target, positions)
495 caches = _inline_cache_entries[deop]
496 if not caches:
497 continue
498 if not show_caches:
499 # We still need to advance the co_positions iterator:
500 for _ in range(caches):
501 next(co_positions, ())
502 continue
503 for name, size in _cache_format[opname[deop]].items():
504 for i in range(size):
505 offset += 2
506 # Only show the fancy argrepr for a CACHE instruction when it's
507 # the first entry for a particular cache value and the
508 # instruction using it is actually quickened:
509 if i == 0 and op != deop:
510 data = code[offset: offset + 2 * size]
511 argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}"
512 else:
513 argrepr = ""
514 yield Instruction(
515 "CACHE", CACHE, 0, None, argrepr, offset, None, False,
516 Positions(*next(co_positions, ()))
517 )
518
519 def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
520 """Disassemble a code object."""
521 linestarts = dict(findlinestarts(co))
522 exception_entries = _parse_exception_table(co)
523 _disassemble_bytes(_get_code_array(co, adaptive),
524 lasti, co._varname_from_oparg,
525 co.co_names, co.co_consts, linestarts, file=file,
526 exception_entries=exception_entries,
527 co_positions=co.co_positions(), show_caches=show_caches)
528
529 def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False):
530 disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive)
531 if depth is None or depth > 0:
532 if depth is not None:
533 depth = depth - 1
534 for x in co.co_consts:
535 if hasattr(x, 'co_code'):
536 print(file=file)
537 print("Disassembly of %r:" % (x,), file=file)
538 _disassemble_recursive(
539 x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive
540 )
541
542 def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
543 names=None, co_consts=None, linestarts=None,
544 *, file=None, line_offset=0, exception_entries=(),
545 co_positions=None, show_caches=False):
546 # Omit the line number column entirely if we have no line number info
547 show_lineno = bool(linestarts)
548 if show_lineno:
549 maxlineno = max(linestarts.values()) + line_offset
550 if maxlineno >= 1000:
551 lineno_width = len(str(maxlineno))
552 else:
553 lineno_width = 3
554 else:
555 lineno_width = 0
556 maxoffset = len(code) - 2
557 if maxoffset >= 10000:
558 offset_width = len(str(maxoffset))
559 else:
560 offset_width = 4
561 for instr in _get_instructions_bytes(code, varname_from_oparg, names,
562 co_consts, linestarts,
563 line_offset=line_offset,
564 exception_entries=exception_entries,
565 co_positions=co_positions,
566 show_caches=show_caches):
567 new_source_line = (show_lineno and
568 instr.starts_line is not None and
569 instr.offset > 0)
570 if new_source_line:
571 print(file=file)
572 is_current_instr = instr.offset == lasti
573 print(instr._disassemble(lineno_width, is_current_instr, offset_width),
574 file=file)
575 if exception_entries:
576 print("ExceptionTable:", file=file)
577 for entry in exception_entries:
578 lasti = " lasti" if entry.lasti else ""
579 end = entry.end-2
580 print(f" {entry.start} to {end} -> {entry.target} [{entry.depth}]{lasti}", file=file)
581
582 def _disassemble_str(source, **kwargs):
583 """Compile the source string, then disassemble the code object."""
584 _disassemble_recursive(_try_compile(source, '<dis>'), **kwargs)
585
586 disco = disassemble # XXX For backwards compatibility
587
588
589 # Rely on C `int` being 32 bits for oparg
590 _INT_BITS = 32
591 # Value for c int when it overflows
592 _INT_OVERFLOW = 2 ** (_INT_BITS - 1)
593
594 def _unpack_opargs(code):
595 extended_arg = 0
596 caches = 0
597 for i in range(0, len(code), 2):
598 # Skip inline CACHE entries:
599 if caches:
600 caches -= 1
601 continue
602 op = code[i]
603 deop = _deoptop(op)
604 caches = _inline_cache_entries[deop]
605 if deop >= HAVE_ARGUMENT:
606 arg = code[i+1] | extended_arg
607 extended_arg = (arg << 8) if deop == EXTENDED_ARG else 0
608 # The oparg is stored as a signed integer
609 # If the value exceeds its upper limit, it will overflow and wrap
610 # to a negative integer
611 if extended_arg >= _INT_OVERFLOW:
612 extended_arg -= 2 * _INT_OVERFLOW
613 else:
614 arg = None
615 extended_arg = 0
616 yield (i, op, arg)
617
618 def findlabels(code):
619 """Detect all offsets in a byte code which are jump targets.
620
621 Return the list of offsets.
622
623 """
624 labels = []
625 for offset, op, arg in _unpack_opargs(code):
626 if arg is not None:
627 if op in hasjrel:
628 if _is_backward_jump(op):
629 arg = -arg
630 label = offset + 2 + arg*2
631 elif op in hasjabs:
632 label = arg*2
633 else:
634 continue
635 if label not in labels:
636 labels.append(label)
637 return labels
638
639 def findlinestarts(code):
640 """Find the offsets in a byte code which are start of lines in the source.
641
642 Generate pairs (offset, lineno)
643 """
644 lastline = None
645 for start, end, line in code.co_lines():
646 if line is not None and line != lastline:
647 lastline = line
648 yield start, line
649 return
650
651 def _find_imports(co):
652 """Find import statements in the code
653
654 Generate triplets (name, level, fromlist) where
655 name is the imported module and level, fromlist are
656 the corresponding args to __import__.
657 """
658 IMPORT_NAME = opmap['IMPORT_NAME']
659 LOAD_CONST = opmap['LOAD_CONST']
660
661 consts = co.co_consts
662 names = co.co_names
663 opargs = [(op, arg) for _, op, arg in _unpack_opargs(co.co_code)
664 if op != EXTENDED_ARG]
665 for i, (op, oparg) in enumerate(opargs):
666 if op == IMPORT_NAME and i >= 2:
667 from_op = opargs[i-1]
668 level_op = opargs[i-2]
669 if (from_op[0] in hasconst and level_op[0] in hasconst):
670 level = _get_const_value(level_op[0], level_op[1], consts)
671 fromlist = _get_const_value(from_op[0], from_op[1], consts)
672 yield (names[oparg], level, fromlist)
673
674 def _find_store_names(co):
675 """Find names of variables which are written in the code
676
677 Generate sequence of strings
678 """
679 STORE_OPS = {
680 opmap['STORE_NAME'],
681 opmap['STORE_GLOBAL']
682 }
683
684 names = co.co_names
685 for _, op, arg in _unpack_opargs(co.co_code):
686 if op in STORE_OPS:
687 yield names[arg]
688
689
690 class ESC[4;38;5;81mBytecode:
691 """The bytecode operations of a piece of code
692
693 Instantiate this with a function, method, other compiled object, string of
694 code, or a code object (as returned by compile()).
695
696 Iterating over this yields the bytecode operations as Instruction instances.
697 """
698 def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False, adaptive=False):
699 self.codeobj = co = _get_code_object(x)
700 if first_line is None:
701 self.first_line = co.co_firstlineno
702 self._line_offset = 0
703 else:
704 self.first_line = first_line
705 self._line_offset = first_line - co.co_firstlineno
706 self._linestarts = dict(findlinestarts(co))
707 self._original_object = x
708 self.current_offset = current_offset
709 self.exception_entries = _parse_exception_table(co)
710 self.show_caches = show_caches
711 self.adaptive = adaptive
712
713 def __iter__(self):
714 co = self.codeobj
715 return _get_instructions_bytes(_get_code_array(co, self.adaptive),
716 co._varname_from_oparg,
717 co.co_names, co.co_consts,
718 self._linestarts,
719 line_offset=self._line_offset,
720 exception_entries=self.exception_entries,
721 co_positions=co.co_positions(),
722 show_caches=self.show_caches)
723
724 def __repr__(self):
725 return "{}({!r})".format(self.__class__.__name__,
726 self._original_object)
727
728 @classmethod
729 def from_traceback(cls, tb, *, show_caches=False, adaptive=False):
730 """ Construct a Bytecode from the given traceback """
731 while tb.tb_next:
732 tb = tb.tb_next
733 return cls(
734 tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, adaptive=adaptive
735 )
736
737 def info(self):
738 """Return formatted information about the code object."""
739 return _format_code_info(self.codeobj)
740
741 def dis(self):
742 """Return a formatted view of the bytecode operations."""
743 co = self.codeobj
744 if self.current_offset is not None:
745 offset = self.current_offset
746 else:
747 offset = -1
748 with io.StringIO() as output:
749 _disassemble_bytes(_get_code_array(co, self.adaptive),
750 varname_from_oparg=co._varname_from_oparg,
751 names=co.co_names, co_consts=co.co_consts,
752 linestarts=self._linestarts,
753 line_offset=self._line_offset,
754 file=output,
755 lasti=offset,
756 exception_entries=self.exception_entries,
757 co_positions=co.co_positions(),
758 show_caches=self.show_caches)
759 return output.getvalue()
760
761
762 def main():
763 import argparse
764
765 parser = argparse.ArgumentParser()
766 parser.add_argument('infile', type=argparse.FileType('rb'), nargs='?', default='-')
767 args = parser.parse_args()
768 with args.infile as infile:
769 source = infile.read()
770 code = compile(source, args.infile.name, "exec")
771 dis(code)
772
773 if __name__ == "__main__":
774 main()