(root)/
Python-3.12.0/
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      _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()