| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- # (C) Copyright 2018 by Daniel Bradburn
- # (C) Copyright 2018, 2020, 2023-2025 by Rocky Bernstein
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- """
- Provide the same API as Python 3.x so xdis can be used as a drop-in
- replacement for ``dis``.
- This will provide ``dis`` module with support for the Python version being run.
- Why would you want this? The main reason is if you want a compatibility shim
- for supporting the more advanced Python3 dis API. That is, to be able to iterate through
- opcodes, supplying a custom file to dump the dis to across python versions. For
- example:
- import xdis.std as dis
- # works in Python 2 and 3
- for op in dis.Bytecode('for i in range(10): pass'):
- print(op)
- There is also the ability to generate a std api for a specific version. For example:
- from xdis.std import make_std_api
- dis = make_std_api(2.4)
- # dis can now disassemble code objects from python 2.4
- Version 'variants' are also supported, for example:
- from xdis.std import make_std_api
- dis = make_std_api(2.6, 'pypy')
- # dis can now disassemble pypy code objects from python 2.6
- """
- # std
- import sys
- # xdis
- from xdis import IS_GRAAL, IS_PYPY
- from xdis.bytecode import Bytecode as _Bytecode, get_optype
- from xdis.cross_dis import (
- code_info as _code_info,
- op_has_argument,
- pretty_flags as _pretty_flags,
- show_code as _show_code,
- xstack_effect as _stack_effect,
- )
- from xdis.disasm import disco as _disco
- from xdis.instruction import Instruction as Instruction_xdis
- from xdis.op_imports import get_opcode_module
- PYPY = "pypy"
- GRAAL = "Graal"
- VARIANT = PYPY if IS_PYPY else None
- VARIANT = GRAAL if IS_GRAAL else None
- class _StdApi:
- def __init__(self, python_version=sys.version_info, variant=VARIANT) -> None:
- if python_version >= (3, 6):
- import xdis.wordcode as xcode
- else:
- import xdis.bytecode as xcode
- self.xcode = xcode
- self.opc = opc = get_opcode_module(python_version, variant)
- self.python_version_tuple = opc.version_tuple
- self.is_pypy = variant == PYPY
- self.is_graal = variant == GRAAL
- self.hasconst = opc.hasconst
- self.hasname = opc.hasname
- self.opmap = opc.opmap
- self.opname = opc.opname
- self.EXTENDED_ARG = opc.EXTENDED_ARG
- self.HAVE_ARGUMENT = opc.HAVE_ARGUMENT
- class Bytecode(_Bytecode):
- """The bytecode operations in a piece of code
- Instantiate this with a function, method, string of code, or a code object
- (as returned by compile()).
- Iterating over these yields a bytecode operation as Instruction instances.
- """
- def __init__(self, x, first_line=None, current_offset=None, opc=None) -> None:
- if opc is None:
- opc = _std_api.opc
- _Bytecode.__init__(
- self,
- x,
- opc=opc,
- first_line=first_line,
- current_offset=current_offset,
- )
- self.Bytecode = Bytecode
- class Instruction(Instruction_xdis):
- """Details for a bytecode operation
- Defined fields:
- opcode - numeric code for operation
- opname - human-readable name 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
- If 3.4 (and changed in 3.11) or greater
- positions
- """
- def __init__(
- self,
- opcode,
- opname,
- arg,
- argval,
- argrepr,
- offset,
- starts_line,
- is_jump_target,
- positions=None,
- **kwargs
- ) -> None:
- has_args = op_has_argument(opcode, self.opc)
- if "has_args" in kwargs:
- assert has_args == kwargs.pop("has_args")
- optype = get_optype(opcode, self.opc)
- if "optype" in kwargs:
- assert optype == kwargs.pop("has_args")
- super().__init(
- opcode,
- opname,
- arg,
- argval,
- argrepr,
- offset,
- starts_line,
- is_jump_target,
- positions,
- optype=optype,
- has_args=has_args,
- **kwargs
- )
- self.opc = opc
- self.Instruction = Instruction
- def _print(self, x: str, file=None) -> None:
- if file is None:
- print(x)
- else:
- file.write(str(x) + "\n")
- def code_info(self, x) -> str:
- """Formatted details of methods, functions, or code."""
- return _code_info(x, self.python_version_tuple)
- def show_code(self, x, file=None) -> None:
- """Print details of methods, functions, or code to *file*.
- If *file* is not provided, the output is printed on stdout.
- """
- return _show_code(x, self.opc.version_tuple, file, is_pypy=self.is_pypy)
- def stack_effect(self, opcode, oparg: int = 0, jump=None):
- """Compute the stack effect of *opcode* with argument *oparg*."""
- return _stack_effect(opcode, self.opc, oparg, jump)
- def pretty_flags(self, flags) -> str:
- """Return pretty representation of code flags."""
- return _pretty_flags(flags)
- def dis(self, x=None, file=None) -> None:
- """Disassemble classes, methods, functions, generators, or code.
- With no argument, disassemble the last traceback.
- """
- self._print(self.Bytecode(x).dis(), file)
- def distb(self, tb=None, file=None) -> None:
- """Disassemble a traceback (default: last traceback)."""
- if tb is None:
- try:
- tb = sys.last_traceback
- except AttributeError:
- raise RuntimeError("no last traceback to disassemble")
- while tb.tb_next:
- tb = tb.tb_next
- self.disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file)
- def disassemble(self, code, lasti: int=-1, file=None) -> None:
- """Disassemble a code object."""
- return self.disco(code, lasti, file)
- def disco(self, code, lasti=-1, file=None) -> None:
- """Disassemble a code object."""
- return _disco(
- self.python_version_tuple,
- code,
- timestamp=None,
- out=file,
- is_pypy=self.is_pypy,
- is_graal=self.is_graal,
- )
- def get_instructions(self, x, first_line=None):
- """Iterator for the opcodes in methods, functions or code
- Generates a series of Instruction named tuples giving the details of
- each operation in the supplied code.
- If *first_line* is not None, it indicates the line number that should
- be reported for the first source line in the disassembled code.
- Otherwise, the source line information (if any) is taken directly from
- the disassembled code object.
- """
- return self.Bytecode(x).get_instructions(x, first_line)
- def findlinestarts(self, code):
- """Find the offsets in a byte code which are start of lines in the source.
- Generate pairs (offset, lineno) as described in Python/compile.c.
- """
- return self.opc.findlinestarts(code)
- def findlabels(self, code):
- """Detect all offsets in a byte code which are jump targets.
- Return the list of offsets.
- """
- return self.opc.findlabels(code, self.opc)
- def make_std_api(python_version=sys.version_info, variant=VARIANT):
- """
- Generate an object which can be used in the same way as the Python
- standard ``dis`` module.
- The difference is that the generated 'module' can be
- used to disassemble byte / word code from a different python version than
- the version of the interpreter under which we are running.
- :param python_version: Generate a dis module for this version of python
- (defaults to the currently running python version.)
- :param variant: The string denoting the variant of the python version
- being run, for example 'pypy' or 'alpha0', 'rc1' etc.,
- or None to auto-detect based on the currently running
- interpreter.
- :return: An Object which can be used like the std ``dis`` module.
- """
- if isinstance(python_version, float):
- major = int(python_version)
- minor = int(((python_version - major) + 0.05) * 10)
- python_version = (major, minor)
- return _StdApi(python_version, variant)
- _std_api = make_std_api()
- hasconst = _std_api.hasconst
- hasname = _std_api.hasname
- opmap = _std_api.opmap
- opname = _std_api.opname
- EXTENDED_ARG = _std_api.EXTENDED_ARG
- HAVE_ARGUMENT = _std_api.HAVE_ARGUMENT
- xcode = _std_api.xcode
- opc = _std_api.opc
- Bytecode = _std_api.Bytecode
- Instruction = _std_api.Instruction
- code_info = _std_api.code_info
- _print = _std_api._print
- show_code = _std_api.show_code
- pretty_flags = _std_api.pretty_flags
- dis = _std_api.dis
- distb = _std_api.distb
- disassemble = _std_api.disassemble
- disco = _std_api.disco
- get_instructions = _std_api.get_instructions
- findlinestarts = _std_api.findlinestarts
- findlabels = _std_api.findlabels
|