xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/dis.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1"""Disassembler of Python byte code into mnemonics."""
2
3import sys
4import types
5import collections
6import io
7
8from opcode import *
9from 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
21del _opcodes_all
22
23_have_code = (types.MethodType, types.FunctionType, types.CodeType,
24              classmethod, staticmethod, type)
25
26FORMAT_VALUE = opmap['FORMAT_VALUE']
27FORMAT_VALUE_CONVERTERS = (
28    (None, ''),
29    (str, 'str'),
30    (repr, 'repr'),
31    (ascii, 'ascii'),
32)
33MAKE_FUNCTION = opmap['MAKE_FUNCTION']
34MAKE_FUNCTION_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure')
35
36LOAD_CONST = opmap['LOAD_CONST']
37LOAD_GLOBAL = opmap['LOAD_GLOBAL']
38BINARY_OP = opmap['BINARY_OP']
39JUMP_BACKWARD = opmap['JUMP_BACKWARD']
40
41CACHE = 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("<")]
46for 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
51deoptmap = {
52    specialized: base for base, family in _specializations.items() for specialized in family
53}
54
55def _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
68def 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
113def 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.
126COMPILER_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
139def 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
153class _Unknown:
154    def __repr__(self):
155        return "<unknown>"
156
157# Sentinel to represent values that cannot be calculated
158UNKNOWN = _Unknown()
159
160def _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
183def _deoptop(op):
184    name = _all_opname[op]
185    return _all_opmap[deoptmap[name]] if name in deoptmap else op
186
187def _get_code_array(co, adaptive):
188    return co._co_code_adaptive if adaptive else co.co_code
189
190def code_info(x):
191    """Formatted details of methods, functions, or code."""
192    return _format_code_info(_get_code_object(x))
193
194def _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
226def 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
233Positions = 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
276class Instruction(_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
330def 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
354def _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
369def _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
381def _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
395def _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
404def _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
420def _is_backward_jump(op):
421    return 'JUMP_BACKWARD' in opname[op]
422
423def _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
519def 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
529def _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
542def _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
582def _disassemble_str(source, **kwargs):
583    """Compile the source string, then disassemble the code object."""
584    _disassemble_recursive(_try_compile(source, '<dis>'), **kwargs)
585
586disco = 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
594def _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
618def 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
639def 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
651def _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
674def _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
690class Bytecode:
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
762def _test():
763    """Simple test program to disassemble a file."""
764    import argparse
765
766    parser = argparse.ArgumentParser()
767    parser.add_argument('infile', type=argparse.FileType('rb'), nargs='?', default='-')
768    args = parser.parse_args()
769    with args.infile as infile:
770        source = infile.read()
771    code = compile(source, args.infile.name, "exec")
772    dis(code)
773
774if __name__ == "__main__":
775    _test()
776