mirror of
https://github.com/python/cpython.git
synced 2025-10-14 18:59:46 +00:00
Issue #11816: multiple improvements to the dis module
* get_instructions generator * ability to redirect output to a file * Bytecode and Instruction abstractions Patch by Nick Coghlan, Ryan Kelly and Thomas Kluyver.
This commit is contained in:
parent
9d351332a7
commit
b39fd0c9b8
5 changed files with 772 additions and 190 deletions
|
@ -26,7 +26,8 @@ Example: Given the function :func:`myfunc`::
|
||||||
def myfunc(alist):
|
def myfunc(alist):
|
||||||
return len(alist)
|
return len(alist)
|
||||||
|
|
||||||
the following command can be used to get the disassembly of :func:`myfunc`::
|
the following command can be used to display the disassembly of
|
||||||
|
:func:`myfunc`::
|
||||||
|
|
||||||
>>> dis.dis(myfunc)
|
>>> dis.dis(myfunc)
|
||||||
2 0 LOAD_GLOBAL 0 (len)
|
2 0 LOAD_GLOBAL 0 (len)
|
||||||
|
@ -36,8 +37,62 @@ the following command can be used to get the disassembly of :func:`myfunc`::
|
||||||
|
|
||||||
(The "2" is a line number).
|
(The "2" is a line number).
|
||||||
|
|
||||||
The :mod:`dis` module defines the following functions and constants:
|
Bytecode analysis
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The bytecode analysis API allows pieces of Python code to be wrapped in a
|
||||||
|
:class:`Bytecode` object that provides easy access to details of the
|
||||||
|
compiled code.
|
||||||
|
|
||||||
|
.. class:: Bytecode
|
||||||
|
|
||||||
|
The bytecode operations of a piece of code
|
||||||
|
|
||||||
|
This is a convenient wrapper around many of the functions listed below.
|
||||||
|
Instantiate it with a function, method, string of code, or a code object
|
||||||
|
(as returned by :func:`compile`).
|
||||||
|
|
||||||
|
Iterating over this yields the bytecode operations as :class:`Instruction`
|
||||||
|
instances.
|
||||||
|
|
||||||
|
.. data:: codeobj
|
||||||
|
|
||||||
|
The compiled code object.
|
||||||
|
|
||||||
|
.. method:: display_code(*, file=None)
|
||||||
|
|
||||||
|
Print a formatted view of the bytecode operations, like :func:`dis`.
|
||||||
|
|
||||||
|
.. method:: info()
|
||||||
|
|
||||||
|
Return a formatted multi-line string with detailed information about the
|
||||||
|
code object, like :func:`code_info`.
|
||||||
|
|
||||||
|
.. method:: show_info(*, file=None)
|
||||||
|
|
||||||
|
Print the information about the code object as returned by :meth:`info`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> bytecode = dis.Bytecode(myfunc)
|
||||||
|
>>> for instr in bytecode:
|
||||||
|
... print(instr.opname)
|
||||||
|
...
|
||||||
|
LOAD_GLOBAL
|
||||||
|
LOAD_FAST
|
||||||
|
CALL_FUNCTION
|
||||||
|
RETURN_VALUE
|
||||||
|
|
||||||
|
|
||||||
|
Analysis functions
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The :mod:`dis` module also defines the following analysis functions that
|
||||||
|
convert the input directly to the desired output. They can be useful if
|
||||||
|
only a single operation is being performed, so the intermediate analysis
|
||||||
|
object isn't useful:
|
||||||
|
|
||||||
.. function:: code_info(x)
|
.. function:: code_info(x)
|
||||||
|
|
||||||
|
@ -51,17 +106,21 @@ The :mod:`dis` module defines the following functions and constants:
|
||||||
.. versionadded:: 3.2
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
|
||||||
.. function:: show_code(x)
|
.. function:: show_code(x, *, file=None)
|
||||||
|
|
||||||
Print detailed code object information for the supplied function, method,
|
Print detailed code object information for the supplied function, method,
|
||||||
source code string or code object to stdout.
|
source code string or code object to stdout.
|
||||||
|
|
||||||
This is a convenient shorthand for ``print(code_info(x))``, intended for
|
This is a convenient shorthand for ``print(code_info(x), file=file)``,
|
||||||
interactive exploration at the interpreter prompt.
|
intended for interactive exploration at the interpreter prompt.
|
||||||
|
|
||||||
.. versionadded:: 3.2
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
.. function:: dis(x=None)
|
.. versionchanged:: 3.4
|
||||||
|
Added ``file`` parameter
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: dis(x=None, *, file=None)
|
||||||
|
|
||||||
Disassemble the *x* object. *x* can denote either a module, a class, a
|
Disassemble the *x* object. *x* can denote either a module, a class, a
|
||||||
method, a function, a code object, a string of source code or a byte sequence
|
method, a function, a code object, a string of source code or a byte sequence
|
||||||
|
@ -72,16 +131,28 @@ The :mod:`dis` module defines the following functions and constants:
|
||||||
disassembled. If no object is provided, this function disassembles the last
|
disassembled. If no object is provided, this function disassembles the last
|
||||||
traceback.
|
traceback.
|
||||||
|
|
||||||
|
The disassembly is written as text to the supplied ``file`` argument if
|
||||||
|
provided and to ``sys.stdout`` otherwise.
|
||||||
|
|
||||||
.. function:: distb(tb=None)
|
.. versionchanged:: 3.4
|
||||||
|
Added ``file`` parameter
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: distb(tb=None, *, file=None)
|
||||||
|
|
||||||
Disassemble the top-of-stack function of a traceback, using the last
|
Disassemble the top-of-stack function of a traceback, using the last
|
||||||
traceback if none was passed. The instruction causing the exception is
|
traceback if none was passed. The instruction causing the exception is
|
||||||
indicated.
|
indicated.
|
||||||
|
|
||||||
|
The disassembly is written as text to the supplied ``file`` argument if
|
||||||
|
provided and to ``sys.stdout`` otherwise.
|
||||||
|
|
||||||
.. function:: disassemble(code, lasti=-1)
|
.. versionchanged:: 3.4
|
||||||
disco(code, lasti=-1)
|
Added ``file`` parameter
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: disassemble(code, lasti=-1, *, file=None)
|
||||||
|
disco(code, lasti=-1, *, file=None)
|
||||||
|
|
||||||
Disassemble a code object, indicating the last instruction if *lasti* was
|
Disassemble a code object, indicating the last instruction if *lasti* was
|
||||||
provided. The output is divided in the following columns:
|
provided. The output is divided in the following columns:
|
||||||
|
@ -97,6 +168,26 @@ The :mod:`dis` module defines the following functions and constants:
|
||||||
The parameter interpretation recognizes local and global variable names,
|
The parameter interpretation recognizes local and global variable names,
|
||||||
constant values, branch targets, and compare operators.
|
constant values, branch targets, and compare operators.
|
||||||
|
|
||||||
|
The disassembly is written as text to the supplied ``file`` argument if
|
||||||
|
provided and to ``sys.stdout`` otherwise.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.4
|
||||||
|
Added ``file`` parameter
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: get_instructions(x, *, line_offset=0)
|
||||||
|
|
||||||
|
Return an iterator over the instructions in the supplied function, method,
|
||||||
|
source code string or code object.
|
||||||
|
|
||||||
|
The iterator generates a series of :class:`Instruction` named tuples
|
||||||
|
giving the details of each operation in the supplied code.
|
||||||
|
|
||||||
|
The given *line_offset* is added to the ``starts_line`` attribute of any
|
||||||
|
instructions that start a new line.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
|
||||||
.. function:: findlinestarts(code)
|
.. function:: findlinestarts(code)
|
||||||
|
|
||||||
|
@ -110,62 +201,61 @@ The :mod:`dis` module defines the following functions and constants:
|
||||||
Detect all offsets in the code object *code* which are jump targets, and
|
Detect all offsets in the code object *code* which are jump targets, and
|
||||||
return a list of these offsets.
|
return a list of these offsets.
|
||||||
|
|
||||||
|
|
||||||
.. data:: opname
|
|
||||||
|
|
||||||
Sequence of operation names, indexable using the bytecode.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: opmap
|
|
||||||
|
|
||||||
Dictionary mapping operation names to bytecodes.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: cmp_op
|
|
||||||
|
|
||||||
Sequence of all compare operation names.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: hasconst
|
|
||||||
|
|
||||||
Sequence of bytecodes that have a constant parameter.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: hasfree
|
|
||||||
|
|
||||||
Sequence of bytecodes that access a free variable.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: hasname
|
|
||||||
|
|
||||||
Sequence of bytecodes that access an attribute by name.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: hasjrel
|
|
||||||
|
|
||||||
Sequence of bytecodes that have a relative jump target.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: hasjabs
|
|
||||||
|
|
||||||
Sequence of bytecodes that have an absolute jump target.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: haslocal
|
|
||||||
|
|
||||||
Sequence of bytecodes that access a local variable.
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: hascompare
|
|
||||||
|
|
||||||
Sequence of bytecodes of Boolean operations.
|
|
||||||
|
|
||||||
|
|
||||||
.. _bytecodes:
|
.. _bytecodes:
|
||||||
|
|
||||||
Python Bytecode Instructions
|
Python Bytecode Instructions
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
The :func:`get_instructions` function and :class:`Bytecode` class provide
|
||||||
|
details of bytecode instructions as :class:`Instruction` instances:
|
||||||
|
|
||||||
|
.. class:: Instruction
|
||||||
|
|
||||||
|
Details for a bytecode operation
|
||||||
|
|
||||||
|
.. data:: opcode
|
||||||
|
|
||||||
|
numeric code for operation, corresponding to the opcode values listed
|
||||||
|
below and the bytecode values in the :ref:`opcode_collections`.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: opname
|
||||||
|
|
||||||
|
human readable name for operation
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: arg
|
||||||
|
|
||||||
|
numeric argument to operation (if any), otherwise None
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: argval
|
||||||
|
|
||||||
|
resolved arg value (if known), otherwise same as arg
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: argrepr
|
||||||
|
|
||||||
|
human readable description of operation argument
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: offset
|
||||||
|
|
||||||
|
start index of operation within bytecode sequence
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: starts_line
|
||||||
|
|
||||||
|
line started by this opcode (if any), otherwise None
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: is_jump_target
|
||||||
|
|
||||||
|
True if other code jumps to here, otherwise False
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
|
||||||
The Python compiler currently generates the following bytecode instructions.
|
The Python compiler currently generates the following bytecode instructions.
|
||||||
|
|
||||||
|
|
||||||
|
@ -820,3 +910,62 @@ the more significant byte last.
|
||||||
which don't take arguments ``< HAVE_ARGUMENT`` and those which do ``>=
|
which don't take arguments ``< HAVE_ARGUMENT`` and those which do ``>=
|
||||||
HAVE_ARGUMENT``.
|
HAVE_ARGUMENT``.
|
||||||
|
|
||||||
|
.. _opcode_collections:
|
||||||
|
|
||||||
|
Opcode collections
|
||||||
|
------------------
|
||||||
|
|
||||||
|
These collections are provided for automatic introspection of bytecode
|
||||||
|
instructions:
|
||||||
|
|
||||||
|
.. data:: opname
|
||||||
|
|
||||||
|
Sequence of operation names, indexable using the bytecode.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: opmap
|
||||||
|
|
||||||
|
Dictionary mapping operation names to bytecodes.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: cmp_op
|
||||||
|
|
||||||
|
Sequence of all compare operation names.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: hasconst
|
||||||
|
|
||||||
|
Sequence of bytecodes that have a constant parameter.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: hasfree
|
||||||
|
|
||||||
|
Sequence of bytecodes that access a free variable (note that 'free' in
|
||||||
|
this context refers to names in the current scope that are referenced by
|
||||||
|
inner scopes or names in outer scopes that are referenced from this scope.
|
||||||
|
It does *not* include references to global or builtin scopes).
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: hasname
|
||||||
|
|
||||||
|
Sequence of bytecodes that access an attribute by name.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: hasjrel
|
||||||
|
|
||||||
|
Sequence of bytecodes that have a relative jump target.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: hasjabs
|
||||||
|
|
||||||
|
Sequence of bytecodes that have an absolute jump target.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: haslocal
|
||||||
|
|
||||||
|
Sequence of bytecodes that access a local variable.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: hascompare
|
||||||
|
|
||||||
|
Sequence of bytecodes of Boolean operations.
|
||||||
|
|
|
@ -152,6 +152,21 @@ Improved Modules
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
||||||
|
dis
|
||||||
|
---
|
||||||
|
|
||||||
|
The :mod:`dis` module is now built around an :class:`Instruction` class that
|
||||||
|
provides details of individual bytecode operations and a
|
||||||
|
:func:`get_instructions` iterator that emits the Instruction stream for a
|
||||||
|
given piece of Python code. The various display tools in the :mod:`dis`
|
||||||
|
module have been updated to be based on these new components.
|
||||||
|
|
||||||
|
The new :class:`dis.Bytecode` class provides an object-oriented API for
|
||||||
|
inspecting bytecode, both in human-readable form and for iterating over
|
||||||
|
instructions.
|
||||||
|
|
||||||
|
(Contributed by Nick Coghlan, Ryan Kelly and Thomas Kluyver in :issue:`11816`)
|
||||||
|
|
||||||
doctest
|
doctest
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
335
Lib/dis.py
335
Lib/dis.py
|
@ -2,12 +2,14 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
import collections
|
||||||
|
|
||||||
from opcode import *
|
from opcode import *
|
||||||
from opcode import __all__ as _opcodes_all
|
from opcode import __all__ as _opcodes_all
|
||||||
|
|
||||||
__all__ = ["code_info", "dis", "disassemble", "distb", "disco",
|
__all__ = ["code_info", "dis", "disassemble", "distb", "disco",
|
||||||
"findlinestarts", "findlabels", "show_code"] + _opcodes_all
|
"findlinestarts", "findlabels", "show_code",
|
||||||
|
"get_instructions", "Instruction", "Bytecode"] + _opcodes_all
|
||||||
del _opcodes_all
|
del _opcodes_all
|
||||||
|
|
||||||
_have_code = (types.MethodType, types.FunctionType, types.CodeType, type)
|
_have_code = (types.MethodType, types.FunctionType, types.CodeType, type)
|
||||||
|
@ -25,7 +27,7 @@ def _try_compile(source, name):
|
||||||
c = compile(source, name, 'exec')
|
c = compile(source, name, 'exec')
|
||||||
return c
|
return c
|
||||||
|
|
||||||
def dis(x=None):
|
def dis(x=None, *, file=None):
|
||||||
"""Disassemble classes, methods, functions, or code.
|
"""Disassemble classes, methods, functions, or code.
|
||||||
|
|
||||||
With no argument, disassemble the last traceback.
|
With no argument, disassemble the last traceback.
|
||||||
|
@ -42,23 +44,23 @@ def dis(x=None):
|
||||||
items = sorted(x.__dict__.items())
|
items = sorted(x.__dict__.items())
|
||||||
for name, x1 in items:
|
for name, x1 in items:
|
||||||
if isinstance(x1, _have_code):
|
if isinstance(x1, _have_code):
|
||||||
print("Disassembly of %s:" % name)
|
print("Disassembly of %s:" % name, file=file)
|
||||||
try:
|
try:
|
||||||
dis(x1)
|
dis(x1)
|
||||||
except TypeError as msg:
|
except TypeError as msg:
|
||||||
print("Sorry:", msg)
|
print("Sorry:", msg, file=file)
|
||||||
print()
|
print(file=file)
|
||||||
elif hasattr(x, 'co_code'): # Code object
|
elif hasattr(x, 'co_code'): # Code object
|
||||||
disassemble(x)
|
disassemble(x, file=file)
|
||||||
elif isinstance(x, (bytes, bytearray)): # Raw bytecode
|
elif isinstance(x, (bytes, bytearray)): # Raw bytecode
|
||||||
_disassemble_bytes(x)
|
_disassemble_bytes(x, file=file)
|
||||||
elif isinstance(x, str): # Source code
|
elif isinstance(x, str): # Source code
|
||||||
_disassemble_str(x)
|
_disassemble_str(x, file=file)
|
||||||
else:
|
else:
|
||||||
raise TypeError("don't know how to disassemble %s objects" %
|
raise TypeError("don't know how to disassemble %s objects" %
|
||||||
type(x).__name__)
|
type(x).__name__)
|
||||||
|
|
||||||
def distb(tb=None):
|
def distb(tb=None, *, file=None):
|
||||||
"""Disassemble a traceback (default: last traceback)."""
|
"""Disassemble a traceback (default: last traceback)."""
|
||||||
if tb is None:
|
if tb is None:
|
||||||
try:
|
try:
|
||||||
|
@ -66,7 +68,7 @@ def distb(tb=None):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise RuntimeError("no last traceback to disassemble")
|
raise RuntimeError("no last traceback to disassemble")
|
||||||
while tb.tb_next: tb = tb.tb_next
|
while tb.tb_next: tb = tb.tb_next
|
||||||
disassemble(tb.tb_frame.f_code, tb.tb_lasti)
|
disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file)
|
||||||
|
|
||||||
# The inspect module interrogates this dictionary to build its
|
# The inspect module interrogates this dictionary to build its
|
||||||
# list of CO_* constants. It is also used by pretty_flags to
|
# list of CO_* constants. It is also used by pretty_flags to
|
||||||
|
@ -95,19 +97,22 @@ def pretty_flags(flags):
|
||||||
names.append(hex(flags))
|
names.append(hex(flags))
|
||||||
return ", ".join(names)
|
return ", ".join(names)
|
||||||
|
|
||||||
def code_info(x):
|
def _get_code_object(x):
|
||||||
"""Formatted details of methods, functions, or code."""
|
"""Helper to handle methods, functions, strings and raw code objects"""
|
||||||
if hasattr(x, '__func__'): # Method
|
if hasattr(x, '__func__'): # Method
|
||||||
x = x.__func__
|
x = x.__func__
|
||||||
if hasattr(x, '__code__'): # Function
|
if hasattr(x, '__code__'): # Function
|
||||||
x = x.__code__
|
x = x.__code__
|
||||||
if isinstance(x, str): # Source code
|
if isinstance(x, str): # Source code
|
||||||
x = _try_compile(x, "<code_info>")
|
x = _try_compile(x, "<disassembly>")
|
||||||
if hasattr(x, 'co_code'): # Code object
|
if hasattr(x, 'co_code'): # Code object
|
||||||
return _format_code_info(x)
|
return x
|
||||||
else:
|
raise TypeError("don't know how to disassemble %s objects" %
|
||||||
raise TypeError("don't know how to disassemble %s objects" %
|
type(x).__name__)
|
||||||
type(x).__name__)
|
|
||||||
|
def code_info(x):
|
||||||
|
"""Formatted details of methods, functions, or code."""
|
||||||
|
return _format_code_info(_get_code_object(x))
|
||||||
|
|
||||||
def _format_code_info(co):
|
def _format_code_info(co):
|
||||||
lines = []
|
lines = []
|
||||||
|
@ -140,106 +145,196 @@ def _format_code_info(co):
|
||||||
lines.append("%4d: %s" % i_n)
|
lines.append("%4d: %s" % i_n)
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
def show_code(co):
|
def show_code(co, *, file=None):
|
||||||
"""Print details of methods, functions, or code to stdout."""
|
"""Print details of methods, functions, or code to stdout."""
|
||||||
print(code_info(co))
|
print(code_info(co), file=file)
|
||||||
|
|
||||||
def disassemble(co, lasti=-1):
|
_Instruction = collections.namedtuple("_Instruction",
|
||||||
"""Disassemble a code object."""
|
"opname opcode arg argval argrepr offset starts_line is_jump_target")
|
||||||
code = co.co_code
|
|
||||||
labels = findlabels(code)
|
class Instruction(_Instruction):
|
||||||
|
"""Details for a bytecode operation
|
||||||
|
|
||||||
|
Defined fields:
|
||||||
|
opname - human readable name for operation
|
||||||
|
opcode - numeric code for operation
|
||||||
|
arg - numeric argument to operation (if any), otherwise None
|
||||||
|
argval - resolved arg value (if known), otherwise same as arg
|
||||||
|
argrepr - human readable description of operation argument
|
||||||
|
offset - start index of operation within bytecode sequence
|
||||||
|
starts_line - line started by this opcode (if any), otherwise None
|
||||||
|
is_jump_target - True if other code jumps to here, otherwise False
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _disassemble(self, lineno_width=3, mark_as_current=False):
|
||||||
|
"""Format instruction details for inclusion in disassembly output
|
||||||
|
|
||||||
|
*lineno_width* sets the width of the line number field (0 omits it)
|
||||||
|
*mark_as_current* inserts a '-->' marker arrow as part of the line
|
||||||
|
"""
|
||||||
|
fields = []
|
||||||
|
# Column: Source code line number
|
||||||
|
if lineno_width:
|
||||||
|
if self.starts_line is not None:
|
||||||
|
lineno_fmt = "%%%dd" % lineno_width
|
||||||
|
fields.append(lineno_fmt % self.starts_line)
|
||||||
|
else:
|
||||||
|
fields.append(' ' * lineno_width)
|
||||||
|
# Column: Current instruction indicator
|
||||||
|
if mark_as_current:
|
||||||
|
fields.append('-->')
|
||||||
|
else:
|
||||||
|
fields.append(' ')
|
||||||
|
# Column: Jump target marker
|
||||||
|
if self.is_jump_target:
|
||||||
|
fields.append('>>')
|
||||||
|
else:
|
||||||
|
fields.append(' ')
|
||||||
|
# Column: Instruction offset from start of code sequence
|
||||||
|
fields.append(repr(self.offset).rjust(4))
|
||||||
|
# Column: Opcode name
|
||||||
|
fields.append(self.opname.ljust(20))
|
||||||
|
# Column: Opcode argument
|
||||||
|
if self.arg is not None:
|
||||||
|
fields.append(repr(self.arg).rjust(5))
|
||||||
|
# Column: Opcode argument details
|
||||||
|
if self.argrepr:
|
||||||
|
fields.append('(' + self.argrepr + ')')
|
||||||
|
return ' '.join(fields)
|
||||||
|
|
||||||
|
|
||||||
|
def get_instructions(x, *, line_offset=0):
|
||||||
|
"""Iterator for the opcodes in methods, functions or code
|
||||||
|
|
||||||
|
Generates a series of Instruction named tuples giving the details of
|
||||||
|
each operations in the supplied code.
|
||||||
|
|
||||||
|
The given line offset is added to the 'starts_line' attribute of any
|
||||||
|
instructions that start a new line.
|
||||||
|
"""
|
||||||
|
co = _get_code_object(x)
|
||||||
|
cell_names = co.co_cellvars + co.co_freevars
|
||||||
linestarts = dict(findlinestarts(co))
|
linestarts = dict(findlinestarts(co))
|
||||||
|
return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names,
|
||||||
|
co.co_consts, cell_names, linestarts,
|
||||||
|
line_offset)
|
||||||
|
|
||||||
|
def _get_const_info(const_index, const_list):
|
||||||
|
"""Helper to get optional details about const references
|
||||||
|
|
||||||
|
Returns the dereferenced constant and its repr if the constant
|
||||||
|
list is defined.
|
||||||
|
Otherwise returns the constant index and its repr().
|
||||||
|
"""
|
||||||
|
argval = const_index
|
||||||
|
if const_list is not None:
|
||||||
|
argval = const_list[const_index]
|
||||||
|
return argval, repr(argval)
|
||||||
|
|
||||||
|
def _get_name_info(name_index, name_list):
|
||||||
|
"""Helper to get optional details about named references
|
||||||
|
|
||||||
|
Returns the dereferenced name as both value and repr if the name
|
||||||
|
list is defined.
|
||||||
|
Otherwise returns the name index and its repr().
|
||||||
|
"""
|
||||||
|
argval = name_index
|
||||||
|
if name_list is not None:
|
||||||
|
argval = name_list[name_index]
|
||||||
|
argrepr = argval
|
||||||
|
else:
|
||||||
|
argrepr = repr(argval)
|
||||||
|
return argval, argrepr
|
||||||
|
|
||||||
|
|
||||||
|
def _get_instructions_bytes(code, varnames=None, names=None, constants=None,
|
||||||
|
cells=None, linestarts=None, line_offset=0):
|
||||||
|
"""Iterate over the instructions in a bytecode string.
|
||||||
|
|
||||||
|
Generates a sequence of Instruction namedtuples giving the details of each
|
||||||
|
opcode. Additional information about the code's runtime environment
|
||||||
|
(e.g. variable names, constants) can be specified using optional
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
"""
|
||||||
|
labels = findlabels(code)
|
||||||
|
extended_arg = 0
|
||||||
|
starts_line = None
|
||||||
|
free = None
|
||||||
|
# enumerate() is not an option, since we sometimes process
|
||||||
|
# multiple elements on a single pass through the loop
|
||||||
n = len(code)
|
n = len(code)
|
||||||
i = 0
|
i = 0
|
||||||
extended_arg = 0
|
|
||||||
free = None
|
|
||||||
while i < n:
|
while i < n:
|
||||||
op = code[i]
|
op = code[i]
|
||||||
if i in linestarts:
|
offset = i
|
||||||
if i > 0:
|
if linestarts is not None:
|
||||||
print()
|
starts_line = linestarts.get(i, None)
|
||||||
print("%3d" % linestarts[i], end=' ')
|
if starts_line is not None:
|
||||||
else:
|
starts_line += line_offset
|
||||||
print(' ', end=' ')
|
is_jump_target = i in labels
|
||||||
|
|
||||||
if i == lasti: print('-->', end=' ')
|
|
||||||
else: print(' ', end=' ')
|
|
||||||
if i in labels: print('>>', end=' ')
|
|
||||||
else: print(' ', end=' ')
|
|
||||||
print(repr(i).rjust(4), end=' ')
|
|
||||||
print(opname[op].ljust(20), end=' ')
|
|
||||||
i = i+1
|
i = i+1
|
||||||
|
arg = None
|
||||||
|
argval = None
|
||||||
|
argrepr = ''
|
||||||
if op >= HAVE_ARGUMENT:
|
if op >= HAVE_ARGUMENT:
|
||||||
oparg = code[i] + code[i+1]*256 + extended_arg
|
arg = code[i] + code[i+1]*256 + extended_arg
|
||||||
extended_arg = 0
|
extended_arg = 0
|
||||||
i = i+2
|
i = i+2
|
||||||
if op == EXTENDED_ARG:
|
if op == EXTENDED_ARG:
|
||||||
extended_arg = oparg*65536
|
extended_arg = arg*65536
|
||||||
print(repr(oparg).rjust(5), end=' ')
|
# Set argval to the dereferenced value of the argument when
|
||||||
|
# availabe, and argrepr to the string representation of argval.
|
||||||
|
# _disassemble_bytes needs the string repr of the
|
||||||
|
# raw name index for LOAD_GLOBAL, LOAD_CONST, etc.
|
||||||
|
argval = arg
|
||||||
if op in hasconst:
|
if op in hasconst:
|
||||||
print('(' + repr(co.co_consts[oparg]) + ')', end=' ')
|
argval, argrepr = _get_const_info(arg, constants)
|
||||||
elif op in hasname:
|
elif op in hasname:
|
||||||
print('(' + co.co_names[oparg] + ')', end=' ')
|
argval, argrepr = _get_name_info(arg, names)
|
||||||
elif op in hasjrel:
|
elif op in hasjrel:
|
||||||
print('(to ' + repr(i + oparg) + ')', end=' ')
|
argval = i + arg
|
||||||
|
argrepr = "to " + repr(argval)
|
||||||
elif op in haslocal:
|
elif op in haslocal:
|
||||||
print('(' + co.co_varnames[oparg] + ')', end=' ')
|
argval, argrepr = _get_name_info(arg, varnames)
|
||||||
elif op in hascompare:
|
elif op in hascompare:
|
||||||
print('(' + cmp_op[oparg] + ')', end=' ')
|
argval = cmp_op[arg]
|
||||||
|
argrepr = argval
|
||||||
elif op in hasfree:
|
elif op in hasfree:
|
||||||
if free is None:
|
argval, argrepr = _get_name_info(arg, cells)
|
||||||
free = co.co_cellvars + co.co_freevars
|
|
||||||
print('(' + free[oparg] + ')', end=' ')
|
|
||||||
elif op in hasnargs:
|
elif op in hasnargs:
|
||||||
print('(%d positional, %d keyword pair)'
|
argrepr = "%d positional, %d keyword pair" % (code[i-2], code[i-1])
|
||||||
% (code[i-2], code[i-1]), end=' ')
|
yield Instruction(opname[op], op,
|
||||||
print()
|
arg, argval, argrepr,
|
||||||
|
offset, starts_line, is_jump_target)
|
||||||
|
|
||||||
|
def disassemble(co, lasti=-1, *, file=None):
|
||||||
|
"""Disassemble a code object."""
|
||||||
|
cell_names = co.co_cellvars + co.co_freevars
|
||||||
|
linestarts = dict(findlinestarts(co))
|
||||||
|
_disassemble_bytes(co.co_code, lasti, co.co_varnames, co.co_names,
|
||||||
|
co.co_consts, cell_names, linestarts, file=file)
|
||||||
|
|
||||||
def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
|
def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
|
||||||
constants=None):
|
constants=None, cells=None, linestarts=None,
|
||||||
labels = findlabels(code)
|
*, file=None):
|
||||||
n = len(code)
|
# Omit the line number column entirely if we have no line number info
|
||||||
i = 0
|
show_lineno = linestarts is not None
|
||||||
while i < n:
|
# TODO?: Adjust width upwards if max(linestarts.values()) >= 1000?
|
||||||
op = code[i]
|
lineno_width = 3 if show_lineno else 0
|
||||||
if i == lasti: print('-->', end=' ')
|
for instr in _get_instructions_bytes(code, varnames, names,
|
||||||
else: print(' ', end=' ')
|
constants, cells, linestarts):
|
||||||
if i in labels: print('>>', end=' ')
|
new_source_line = (show_lineno and
|
||||||
else: print(' ', end=' ')
|
instr.starts_line is not None and
|
||||||
print(repr(i).rjust(4), end=' ')
|
instr.offset > 0)
|
||||||
print(opname[op].ljust(15), end=' ')
|
if new_source_line:
|
||||||
i = i+1
|
print(file=file)
|
||||||
if op >= HAVE_ARGUMENT:
|
is_current_instr = instr.offset == lasti
|
||||||
oparg = code[i] + code[i+1]*256
|
print(instr._disassemble(lineno_width, is_current_instr), file=file)
|
||||||
i = i+2
|
|
||||||
print(repr(oparg).rjust(5), end=' ')
|
|
||||||
if op in hasconst:
|
|
||||||
if constants:
|
|
||||||
print('(' + repr(constants[oparg]) + ')', end=' ')
|
|
||||||
else:
|
|
||||||
print('(%d)'%oparg, end=' ')
|
|
||||||
elif op in hasname:
|
|
||||||
if names is not None:
|
|
||||||
print('(' + names[oparg] + ')', end=' ')
|
|
||||||
else:
|
|
||||||
print('(%d)'%oparg, end=' ')
|
|
||||||
elif op in hasjrel:
|
|
||||||
print('(to ' + repr(i + oparg) + ')', end=' ')
|
|
||||||
elif op in haslocal:
|
|
||||||
if varnames:
|
|
||||||
print('(' + varnames[oparg] + ')', end=' ')
|
|
||||||
else:
|
|
||||||
print('(%d)' % oparg, end=' ')
|
|
||||||
elif op in hascompare:
|
|
||||||
print('(' + cmp_op[oparg] + ')', end=' ')
|
|
||||||
elif op in hasnargs:
|
|
||||||
print('(%d positional, %d keyword pair)'
|
|
||||||
% (code[i-2], code[i-1]), end=' ')
|
|
||||||
print()
|
|
||||||
|
|
||||||
def _disassemble_str(source):
|
def _disassemble_str(source, *, file=None):
|
||||||
"""Compile the source string, then disassemble the code object."""
|
"""Compile the source string, then disassemble the code object."""
|
||||||
disassemble(_try_compile(source, '<dis>'))
|
disassemble(_try_compile(source, '<dis>'), file=file)
|
||||||
|
|
||||||
disco = disassemble # XXX For backwards compatibility
|
disco = disassemble # XXX For backwards compatibility
|
||||||
|
|
||||||
|
@ -250,19 +345,21 @@ def findlabels(code):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
labels = []
|
labels = []
|
||||||
|
# enumerate() is not an option, since we sometimes process
|
||||||
|
# multiple elements on a single pass through the loop
|
||||||
n = len(code)
|
n = len(code)
|
||||||
i = 0
|
i = 0
|
||||||
while i < n:
|
while i < n:
|
||||||
op = code[i]
|
op = code[i]
|
||||||
i = i+1
|
i = i+1
|
||||||
if op >= HAVE_ARGUMENT:
|
if op >= HAVE_ARGUMENT:
|
||||||
oparg = code[i] + code[i+1]*256
|
arg = code[i] + code[i+1]*256
|
||||||
i = i+2
|
i = i+2
|
||||||
label = -1
|
label = -1
|
||||||
if op in hasjrel:
|
if op in hasjrel:
|
||||||
label = i+oparg
|
label = i+arg
|
||||||
elif op in hasjabs:
|
elif op in hasjabs:
|
||||||
label = oparg
|
label = arg
|
||||||
if label >= 0:
|
if label >= 0:
|
||||||
if label not in labels:
|
if label not in labels:
|
||||||
labels.append(label)
|
labels.append(label)
|
||||||
|
@ -290,6 +387,50 @@ def findlinestarts(code):
|
||||||
if lineno != lastlineno:
|
if lineno != lastlineno:
|
||||||
yield (addr, lineno)
|
yield (addr, lineno)
|
||||||
|
|
||||||
|
class Bytecode:
|
||||||
|
"""The bytecode operations of a piece of code
|
||||||
|
|
||||||
|
Instantiate this with a function, method, string of code, or a code object
|
||||||
|
(as returned by compile()).
|
||||||
|
|
||||||
|
Iterating over this yields the bytecode operations as Instruction instances.
|
||||||
|
"""
|
||||||
|
def __init__(self, x):
|
||||||
|
self.codeobj = _get_code_object(x)
|
||||||
|
self.cell_names = self.codeobj.co_cellvars + self.codeobj.co_freevars
|
||||||
|
self.linestarts = dict(findlinestarts(self.codeobj))
|
||||||
|
self.line_offset = 0
|
||||||
|
self.original_object = x
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
co = self.codeobj
|
||||||
|
return _get_instructions_bytes(co.co_code, co.co_varnames, co.co_names,
|
||||||
|
co.co_consts, self.cell_names,
|
||||||
|
self.linestarts, self.line_offset)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}({!r})".format(self.__class__.__name__, self.original_object)
|
||||||
|
|
||||||
|
def info(self):
|
||||||
|
"""Return formatted information about the code object."""
|
||||||
|
return _format_code_info(self.codeobj)
|
||||||
|
|
||||||
|
def show_info(self, *, file=None):
|
||||||
|
"""Print the information about the code object as returned by info()."""
|
||||||
|
print(self.info(), file=file)
|
||||||
|
|
||||||
|
def display_code(self, *, file=None):
|
||||||
|
"""Print a formatted view of the bytecode operations.
|
||||||
|
"""
|
||||||
|
co = self.codeobj
|
||||||
|
return _disassemble_bytes(co.co_code, varnames=co.co_varnames,
|
||||||
|
names=co.co_names, constants=co.co_consts,
|
||||||
|
cells=self.cell_names,
|
||||||
|
linestarts=self.linestarts,
|
||||||
|
file=file
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _test():
|
def _test():
|
||||||
"""Simple test program to disassemble a file."""
|
"""Simple test program to disassemble a file."""
|
||||||
if sys.argv[1:]:
|
if sys.argv[1:]:
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
# Minimal tests for dis module
|
# Minimal tests for dis module
|
||||||
|
|
||||||
from test.support import run_unittest, captured_stdout
|
from test.support import run_unittest, captured_stdout
|
||||||
|
from test.bytecode_helper import BytecodeTestCase
|
||||||
import difflib
|
import difflib
|
||||||
import unittest
|
import unittest
|
||||||
import sys
|
import sys
|
||||||
import dis
|
import dis
|
||||||
import io
|
import io
|
||||||
|
import types
|
||||||
|
|
||||||
class _C:
|
class _C:
|
||||||
def __init__(self, x):
|
def __init__(self, x):
|
||||||
|
@ -22,12 +24,12 @@ dis_c_instance_method = """\
|
||||||
""" % (_C.__init__.__code__.co_firstlineno + 1,)
|
""" % (_C.__init__.__code__.co_firstlineno + 1,)
|
||||||
|
|
||||||
dis_c_instance_method_bytes = """\
|
dis_c_instance_method_bytes = """\
|
||||||
0 LOAD_FAST 1 (1)
|
0 LOAD_FAST 1 (1)
|
||||||
3 LOAD_CONST 1 (1)
|
3 LOAD_CONST 1 (1)
|
||||||
6 COMPARE_OP 2 (==)
|
6 COMPARE_OP 2 (==)
|
||||||
9 LOAD_FAST 0 (0)
|
9 LOAD_FAST 0 (0)
|
||||||
12 STORE_ATTR 0 (0)
|
12 STORE_ATTR 0 (0)
|
||||||
15 LOAD_CONST 0 (0)
|
15 LOAD_CONST 0 (0)
|
||||||
18 RETURN_VALUE
|
18 RETURN_VALUE
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -48,11 +50,11 @@ dis_f = """\
|
||||||
|
|
||||||
|
|
||||||
dis_f_co_code = """\
|
dis_f_co_code = """\
|
||||||
0 LOAD_GLOBAL 0 (0)
|
0 LOAD_GLOBAL 0 (0)
|
||||||
3 LOAD_FAST 0 (0)
|
3 LOAD_FAST 0 (0)
|
||||||
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
||||||
9 POP_TOP
|
9 POP_TOP
|
||||||
10 LOAD_CONST 1 (1)
|
10 LOAD_CONST 1 (1)
|
||||||
13 RETURN_VALUE
|
13 RETURN_VALUE
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -298,29 +300,16 @@ Filename: (.*)
|
||||||
Argument count: 1
|
Argument count: 1
|
||||||
Kw-only arguments: 0
|
Kw-only arguments: 0
|
||||||
Number of locals: 1
|
Number of locals: 1
|
||||||
Stack size: 4
|
Stack size: 3
|
||||||
Flags: OPTIMIZED, NEWLOCALS, NOFREE
|
Flags: OPTIMIZED, NEWLOCALS, NOFREE
|
||||||
Constants:
|
Constants:
|
||||||
0: %r
|
0: %r
|
||||||
1: '__func__'
|
Names:
|
||||||
2: '__code__'
|
0: _format_code_info
|
||||||
3: '<code_info>'
|
1: _get_code_object
|
||||||
4: 'co_code'
|
|
||||||
5: "don't know how to disassemble %%s objects"
|
|
||||||
%sNames:
|
|
||||||
0: hasattr
|
|
||||||
1: __func__
|
|
||||||
2: __code__
|
|
||||||
3: isinstance
|
|
||||||
4: str
|
|
||||||
5: _try_compile
|
|
||||||
6: _format_code_info
|
|
||||||
7: TypeError
|
|
||||||
8: type
|
|
||||||
9: __name__
|
|
||||||
Variable names:
|
Variable names:
|
||||||
0: x""" % (('Formatted details of methods, functions, or code.', ' 6: None\n')
|
0: x""" % (('Formatted details of methods, functions, or code.',)
|
||||||
if sys.flags.optimize < 2 else (None, ''))
|
if sys.flags.optimize < 2 else (None,))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def tricky(x, y, z=True, *args, c, d, e=[], **kwds):
|
def tricky(x, y, z=True, *args, c, d, e=[], **kwds):
|
||||||
|
@ -384,7 +373,7 @@ Free variables:
|
||||||
|
|
||||||
code_info_expr_str = """\
|
code_info_expr_str = """\
|
||||||
Name: <module>
|
Name: <module>
|
||||||
Filename: <code_info>
|
Filename: <disassembly>
|
||||||
Argument count: 0
|
Argument count: 0
|
||||||
Kw-only arguments: 0
|
Kw-only arguments: 0
|
||||||
Number of locals: 0
|
Number of locals: 0
|
||||||
|
@ -397,7 +386,7 @@ Names:
|
||||||
|
|
||||||
code_info_simple_stmt_str = """\
|
code_info_simple_stmt_str = """\
|
||||||
Name: <module>
|
Name: <module>
|
||||||
Filename: <code_info>
|
Filename: <disassembly>
|
||||||
Argument count: 0
|
Argument count: 0
|
||||||
Kw-only arguments: 0
|
Kw-only arguments: 0
|
||||||
Number of locals: 0
|
Number of locals: 0
|
||||||
|
@ -411,7 +400,7 @@ Names:
|
||||||
|
|
||||||
code_info_compound_stmt_str = """\
|
code_info_compound_stmt_str = """\
|
||||||
Name: <module>
|
Name: <module>
|
||||||
Filename: <code_info>
|
Filename: <disassembly>
|
||||||
Argument count: 0
|
Argument count: 0
|
||||||
Kw-only arguments: 0
|
Kw-only arguments: 0
|
||||||
Number of locals: 0
|
Number of locals: 0
|
||||||
|
@ -445,6 +434,9 @@ class CodeInfoTests(unittest.TestCase):
|
||||||
with captured_stdout() as output:
|
with captured_stdout() as output:
|
||||||
dis.show_code(x)
|
dis.show_code(x)
|
||||||
self.assertRegex(output.getvalue(), expected+"\n")
|
self.assertRegex(output.getvalue(), expected+"\n")
|
||||||
|
output = io.StringIO()
|
||||||
|
dis.show_code(x, file=output)
|
||||||
|
self.assertRegex(output.getvalue(), expected)
|
||||||
|
|
||||||
def test_code_info_object(self):
|
def test_code_info_object(self):
|
||||||
self.assertRaises(TypeError, dis.code_info, object())
|
self.assertRaises(TypeError, dis.code_info, object())
|
||||||
|
@ -453,8 +445,289 @@ class CodeInfoTests(unittest.TestCase):
|
||||||
self.assertEqual(dis.pretty_flags(0), '0x0')
|
self.assertEqual(dis.pretty_flags(0), '0x0')
|
||||||
|
|
||||||
|
|
||||||
|
# Fodder for instruction introspection tests
|
||||||
|
# Editing any of these may require recalculating the expected output
|
||||||
|
def outer(a=1, b=2):
|
||||||
|
def f(c=3, d=4):
|
||||||
|
def inner(e=5, f=6):
|
||||||
|
print(a, b, c, d, e, f)
|
||||||
|
print(a, b, c, d)
|
||||||
|
return inner
|
||||||
|
print(a, b, '', 1, [], {}, "Hello world!")
|
||||||
|
return f
|
||||||
|
|
||||||
|
def jumpy():
|
||||||
|
# This won't actually run (but that's OK, we only disassemble it)
|
||||||
|
for i in range(10):
|
||||||
|
print(i)
|
||||||
|
if i < 4:
|
||||||
|
continue
|
||||||
|
if i > 6:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("I can haz else clause?")
|
||||||
|
while i:
|
||||||
|
print(i)
|
||||||
|
i -= 1
|
||||||
|
if i > 6:
|
||||||
|
continue
|
||||||
|
if i < 4:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Who let lolcatz into this test suite?")
|
||||||
|
try:
|
||||||
|
1 / 0
|
||||||
|
except ZeroDivisionError:
|
||||||
|
print("Here we go, here we go, here we go...")
|
||||||
|
else:
|
||||||
|
with i as dodgy:
|
||||||
|
print("Never reach this")
|
||||||
|
finally:
|
||||||
|
print("OK, now we're done")
|
||||||
|
|
||||||
|
# End fodder for opinfo generation tests
|
||||||
|
expected_outer_offset = 1 - outer.__code__.co_firstlineno
|
||||||
|
expected_jumpy_offset = 1 - jumpy.__code__.co_firstlineno
|
||||||
|
code_object_f = outer.__code__.co_consts[3]
|
||||||
|
code_object_inner = code_object_f.co_consts[3]
|
||||||
|
|
||||||
|
# The following lines are useful to regenerate the expected results after
|
||||||
|
# either the fodder is modified or the bytecode generation changes
|
||||||
|
# After regeneration, update the references to code_object_f and
|
||||||
|
# code_object_inner before rerunning the tests
|
||||||
|
|
||||||
|
#_instructions = dis.get_instructions(outer, line_offset=expected_outer_offset)
|
||||||
|
#print('expected_opinfo_outer = [\n ',
|
||||||
|
#',\n '.join(map(str, _instructions)), ',\n]', sep='')
|
||||||
|
#_instructions = dis.get_instructions(outer(), line_offset=expected_outer_offset)
|
||||||
|
#print('expected_opinfo_f = [\n ',
|
||||||
|
#',\n '.join(map(str, _instructions)), ',\n]', sep='')
|
||||||
|
#_instructions = dis.get_instructions(outer()(), line_offset=expected_outer_offset)
|
||||||
|
#print('expected_opinfo_inner = [\n ',
|
||||||
|
#',\n '.join(map(str, _instructions)), ',\n]', sep='')
|
||||||
|
#_instructions = dis.get_instructions(jumpy, line_offset=expected_jumpy_offset)
|
||||||
|
#print('expected_opinfo_jumpy = [\n ',
|
||||||
|
#',\n '.join(map(str, _instructions)), ',\n]', sep='')
|
||||||
|
|
||||||
|
|
||||||
|
Instruction = dis.Instruction
|
||||||
|
expected_opinfo_outer = [
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=3, argrepr='3', offset=0, starts_line=2, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=3, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CLOSURE', opcode=135, arg=0, argval='a', argrepr='a', offset=6, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CLOSURE', opcode=135, arg=1, argval='b', argrepr='b', offset=9, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='BUILD_TUPLE', opcode=102, arg=2, argval=2, argrepr='', offset=12, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_f, argrepr=repr(code_object_f), offset=15, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='outer.<locals>.f', argrepr="'outer.<locals>.f'", offset=18, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='MAKE_CLOSURE', opcode=134, arg=2, argval=2, argrepr='', offset=21, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='f', argrepr='f', offset=24, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=27, starts_line=7, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='a', argrepr='a', offset=30, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='b', argrepr='b', offset=33, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval='', argrepr="''", offset=36, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval=1, argrepr='1', offset=39, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='BUILD_LIST', opcode=103, arg=0, argval=0, argrepr='', offset=42, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='BUILD_MAP', opcode=105, arg=0, argval=0, argrepr='', offset=45, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval='Hello world!', argrepr="'Hello world!'", offset=48, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=7, argval=7, argrepr='7 positional, 0 keyword pair', offset=51, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=54, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='f', argrepr='f', offset=55, starts_line=8, is_jump_target=False),
|
||||||
|
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=58, starts_line=None, is_jump_target=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
expected_opinfo_f = [
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=5, argrepr='5', offset=0, starts_line=3, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=6, argrepr='6', offset=3, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CLOSURE', opcode=135, arg=2, argval='a', argrepr='a', offset=6, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CLOSURE', opcode=135, arg=3, argval='b', argrepr='b', offset=9, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CLOSURE', opcode=135, arg=0, argval='c', argrepr='c', offset=12, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CLOSURE', opcode=135, arg=1, argval='d', argrepr='d', offset=15, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='BUILD_TUPLE', opcode=102, arg=4, argval=4, argrepr='', offset=18, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_inner, argrepr=repr(code_object_inner), offset=21, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='outer.<locals>.f.<locals>.inner', argrepr="'outer.<locals>.f.<locals>.inner'", offset=24, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='MAKE_CLOSURE', opcode=134, arg=2, argval=2, argrepr='', offset=27, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='inner', argrepr='inner', offset=30, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=33, starts_line=5, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=2, argval='a', argrepr='a', offset=36, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=3, argval='b', argrepr='b', offset=39, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='c', argrepr='c', offset=42, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='d', argrepr='d', offset=45, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=4, argval=4, argrepr='4 positional, 0 keyword pair', offset=48, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=51, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='inner', argrepr='inner', offset=52, starts_line=6, is_jump_target=False),
|
||||||
|
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=55, starts_line=None, is_jump_target=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
expected_opinfo_inner = [
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=0, starts_line=4, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='a', argrepr='a', offset=3, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='b', argrepr='b', offset=6, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=2, argval='c', argrepr='c', offset=9, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_DEREF', opcode=136, arg=3, argval='d', argrepr='d', offset=12, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='e', argrepr='e', offset=15, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=1, argval='f', argrepr='f', offset=18, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=6, argval=6, argrepr='6 positional, 0 keyword pair', offset=21, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=24, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=25, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=28, starts_line=None, is_jump_target=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
expected_opinfo_jumpy = [
|
||||||
|
Instruction(opname='SETUP_LOOP', opcode=120, arg=74, argval=77, argrepr='to 77', offset=0, starts_line=3, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='range', argrepr='range', offset=3, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=10, argrepr='10', offset=6, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=9, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='GET_ITER', opcode=68, arg=None, argval=None, argrepr='', offset=12, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='FOR_ITER', opcode=93, arg=50, argval=66, argrepr='to 66', offset=13, starts_line=None, is_jump_target=True),
|
||||||
|
Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=16, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=19, starts_line=4, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=22, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=25, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=28, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=29, starts_line=5, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=32, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='COMPARE_OP', opcode=107, arg=0, argval='<', argrepr='<', offset=35, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=47, argval=47, argrepr='', offset=38, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=13, argval=13, argrepr='', offset=41, starts_line=6, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_FORWARD', opcode=110, arg=0, argval=47, argrepr='to 47', offset=44, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=47, starts_line=7, is_jump_target=True),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=50, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='COMPARE_OP', opcode=107, arg=4, argval='>', argrepr='>', offset=53, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=13, argval=13, argrepr='', offset=56, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='BREAK_LOOP', opcode=80, arg=None, argval=None, argrepr='', offset=59, starts_line=8, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=13, argval=13, argrepr='', offset=60, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=13, argval=13, argrepr='', offset=63, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=66, starts_line=None, is_jump_target=True),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=67, starts_line=10, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=70, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=73, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=76, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='SETUP_LOOP', opcode=120, arg=74, argval=154, argrepr='to 154', offset=77, starts_line=11, is_jump_target=True),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=80, starts_line=None, is_jump_target=True),
|
||||||
|
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=143, argval=143, argrepr='', offset=83, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=86, starts_line=12, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=89, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=92, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=95, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=96, starts_line=13, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=99, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='INPLACE_SUBTRACT', opcode=56, arg=None, argval=None, argrepr='', offset=102, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=103, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=106, starts_line=14, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=109, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='COMPARE_OP', opcode=107, arg=4, argval='>', argrepr='>', offset=112, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=124, argval=124, argrepr='', offset=115, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=80, argval=80, argrepr='', offset=118, starts_line=15, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_FORWARD', opcode=110, arg=0, argval=124, argrepr='to 124', offset=121, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=124, starts_line=16, is_jump_target=True),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=127, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='COMPARE_OP', opcode=107, arg=0, argval='<', argrepr='<', offset=130, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=80, argval=80, argrepr='', offset=133, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='BREAK_LOOP', opcode=80, arg=None, argval=None, argrepr='', offset=136, starts_line=17, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=80, argval=80, argrepr='', offset=137, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=80, argval=80, argrepr='', offset=140, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=143, starts_line=None, is_jump_target=True),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=144, starts_line=19, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=147, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=150, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=153, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='SETUP_FINALLY', opcode=122, arg=72, argval=229, argrepr='to 229', offset=154, starts_line=20, is_jump_target=True),
|
||||||
|
Instruction(opname='SETUP_EXCEPT', opcode=121, arg=12, argval=172, argrepr='to 172', offset=157, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=160, starts_line=21, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval=0, argrepr='0', offset=163, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='BINARY_TRUE_DIVIDE', opcode=27, arg=None, argval=None, argrepr='', offset=166, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=167, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=168, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_FORWARD', opcode=110, arg=28, argval=200, argrepr='to 200', offset=169, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=172, starts_line=22, is_jump_target=True),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=2, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=173, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='COMPARE_OP', opcode=107, arg=10, argval='exception match', argrepr='exception match', offset=176, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=199, argval=199, argrepr='', offset=179, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=182, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=183, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=184, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=185, starts_line=23, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=188, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=191, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=194, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=195, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='JUMP_FORWARD', opcode=110, arg=26, argval=225, argrepr='to 225', offset=196, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=199, starts_line=None, is_jump_target=True),
|
||||||
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=200, starts_line=25, is_jump_target=True),
|
||||||
|
Instruction(opname='SETUP_WITH', opcode=143, arg=17, argval=223, argrepr='to 223', offset=203, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='STORE_FAST', opcode=125, arg=1, argval='dodgy', argrepr='dodgy', offset=206, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=209, starts_line=26, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=9, argval='Never reach this', argrepr="'Never reach this'", offset=212, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=215, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=218, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=219, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=220, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='WITH_CLEANUP', opcode=81, arg=None, argval=None, argrepr='', offset=223, starts_line=None, is_jump_target=True),
|
||||||
|
Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=224, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=225, starts_line=None, is_jump_target=True),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=226, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=229, starts_line=28, is_jump_target=True),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=232, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=235, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=238, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='END_FINALLY', opcode=88, arg=None, argval=None, argrepr='', offset=239, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=240, starts_line=None, is_jump_target=False),
|
||||||
|
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=243, starts_line=None, is_jump_target=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
class InstructionTests(BytecodeTestCase):
|
||||||
|
def test_outer(self):
|
||||||
|
self.assertBytecodeExactlyMatches(outer, expected_opinfo_outer,
|
||||||
|
line_offset=expected_outer_offset)
|
||||||
|
|
||||||
|
def test_nested(self):
|
||||||
|
with captured_stdout():
|
||||||
|
f = outer()
|
||||||
|
self.assertBytecodeExactlyMatches(f, expected_opinfo_f,
|
||||||
|
line_offset=expected_outer_offset)
|
||||||
|
|
||||||
|
def test_doubly_nested(self):
|
||||||
|
with captured_stdout():
|
||||||
|
inner = outer()()
|
||||||
|
self.assertBytecodeExactlyMatches(inner, expected_opinfo_inner,
|
||||||
|
line_offset=expected_outer_offset)
|
||||||
|
|
||||||
|
def test_jumpy(self):
|
||||||
|
self.assertBytecodeExactlyMatches(jumpy, expected_opinfo_jumpy,
|
||||||
|
line_offset=expected_jumpy_offset)
|
||||||
|
|
||||||
|
class BytecodeTests(unittest.TestCase):
|
||||||
|
def test_instantiation(self):
|
||||||
|
# Test with function, method, code string and code object
|
||||||
|
for obj in [_f, _C(1).__init__, "a=1", _f.__code__]:
|
||||||
|
b = dis.Bytecode(obj)
|
||||||
|
self.assertIsInstance(b.codeobj, types.CodeType)
|
||||||
|
|
||||||
|
self.assertRaises(TypeError, dis.Bytecode, object())
|
||||||
|
|
||||||
|
def test_iteration(self):
|
||||||
|
b = dis.Bytecode(_f)
|
||||||
|
for instr in b:
|
||||||
|
self.assertIsInstance(instr, dis.Instruction)
|
||||||
|
|
||||||
|
assert len(list(b)) > 0 # Iterating should yield at least 1 instruction
|
||||||
|
|
||||||
|
def test_info(self):
|
||||||
|
self.maxDiff = 1000
|
||||||
|
for x, expected in CodeInfoTests.test_pairs:
|
||||||
|
b = dis.Bytecode(x)
|
||||||
|
self.assertRegex(b.info(), expected)
|
||||||
|
|
||||||
|
def test_display_code(self):
|
||||||
|
b = dis.Bytecode(_f)
|
||||||
|
output = io.StringIO()
|
||||||
|
b.display_code(file=output)
|
||||||
|
result = [line.rstrip() for line in output.getvalue().splitlines()]
|
||||||
|
self.assertEqual(result, dis_f.splitlines())
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
run_unittest(DisTests, CodeInfoTests)
|
run_unittest(DisTests, CodeInfoTests, InstructionTests, BytecodeTests)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_main()
|
test_main()
|
||||||
|
|
|
@ -74,6 +74,10 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #11816: multiple improvements to the dis module: get_instructions
|
||||||
|
generator, ability to redirect output to a file, Bytecode and Instruction
|
||||||
|
abstractions. Patch by Nick Coghlan, Ryan Kelly and Thomas Kluyver.
|
||||||
|
|
||||||
- Issue #13831: Embed stringification of remote traceback in local
|
- Issue #13831: Embed stringification of remote traceback in local
|
||||||
traceback raised when pool task raises an exception.
|
traceback raised when pool task raises an exception.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue