(root)/
Python-3.11.7/
Lib/
dis.py
       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()