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