std.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. # (C) Copyright 2018 by Daniel Bradburn
  2. # (C) Copyright 2018, 2020, 2023-2025 by Rocky Bernstein
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; either version 2
  7. # of the License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. """
  18. Provide the same API as Python 3.x so xdis can be used as a drop-in
  19. replacement for ``dis``.
  20. This will provide ``dis`` module with support for the Python version being run.
  21. Why would you want this? The main reason is if you want a compatibility shim
  22. for supporting the more advanced Python3 dis API. That is, to be able to iterate through
  23. opcodes, supplying a custom file to dump the dis to across python versions. For
  24. example:
  25. import xdis.std as dis
  26. # works in Python 2 and 3
  27. for op in dis.Bytecode('for i in range(10): pass'):
  28. print(op)
  29. There is also the ability to generate a std api for a specific version. For example:
  30. from xdis.std import make_std_api
  31. dis = make_std_api(2.4)
  32. # dis can now disassemble code objects from python 2.4
  33. Version 'variants' are also supported, for example:
  34. from xdis.std import make_std_api
  35. dis = make_std_api(2.6, 'pypy')
  36. # dis can now disassemble pypy code objects from python 2.6
  37. """
  38. # std
  39. import sys
  40. # xdis
  41. from xdis import IS_GRAAL, IS_PYPY
  42. from xdis.bytecode import Bytecode as _Bytecode, get_optype
  43. from xdis.cross_dis import (
  44. code_info as _code_info,
  45. op_has_argument,
  46. pretty_flags as _pretty_flags,
  47. show_code as _show_code,
  48. xstack_effect as _stack_effect,
  49. )
  50. from xdis.disasm import disco as _disco
  51. from xdis.instruction import Instruction as Instruction_xdis
  52. from xdis.op_imports import get_opcode_module
  53. PYPY = "pypy"
  54. GRAAL = "Graal"
  55. VARIANT = PYPY if IS_PYPY else None
  56. VARIANT = GRAAL if IS_GRAAL else None
  57. class _StdApi:
  58. def __init__(self, python_version=sys.version_info, variant=VARIANT) -> None:
  59. if python_version >= (3, 6):
  60. import xdis.wordcode as xcode
  61. else:
  62. import xdis.bytecode as xcode
  63. self.xcode = xcode
  64. self.opc = opc = get_opcode_module(python_version, variant)
  65. self.python_version_tuple = opc.version_tuple
  66. self.is_pypy = variant == PYPY
  67. self.is_graal = variant == GRAAL
  68. self.hasconst = opc.hasconst
  69. self.hasname = opc.hasname
  70. self.opmap = opc.opmap
  71. self.opname = opc.opname
  72. self.EXTENDED_ARG = opc.EXTENDED_ARG
  73. self.HAVE_ARGUMENT = opc.HAVE_ARGUMENT
  74. class Bytecode(_Bytecode):
  75. """The bytecode operations in a piece of code
  76. Instantiate this with a function, method, string of code, or a code object
  77. (as returned by compile()).
  78. Iterating over these yields a bytecode operation as Instruction instances.
  79. """
  80. def __init__(self, x, first_line=None, current_offset=None, opc=None) -> None:
  81. if opc is None:
  82. opc = _std_api.opc
  83. _Bytecode.__init__(
  84. self,
  85. x,
  86. opc=opc,
  87. first_line=first_line,
  88. current_offset=current_offset,
  89. )
  90. self.Bytecode = Bytecode
  91. class Instruction(Instruction_xdis):
  92. """Details for a bytecode operation
  93. Defined fields:
  94. opcode - numeric code for operation
  95. opname - human-readable name for operation
  96. arg - numeric argument to operation (if any), otherwise None
  97. argval - resolved arg value (if known), otherwise same as arg
  98. argrepr - human-readable description of operation argument
  99. offset - start index of operation within bytecode sequence
  100. starts_line - line started by this opcode (if any), otherwise None
  101. is_jump_target - True if other code jumps to here, otherwise False
  102. If 3.4 (and changed in 3.11) or greater
  103. positions
  104. """
  105. def __init__(
  106. self,
  107. opcode,
  108. opname,
  109. arg,
  110. argval,
  111. argrepr,
  112. offset,
  113. starts_line,
  114. is_jump_target,
  115. positions=None,
  116. **kwargs
  117. ) -> None:
  118. has_args = op_has_argument(opcode, self.opc)
  119. if "has_args" in kwargs:
  120. assert has_args == kwargs.pop("has_args")
  121. optype = get_optype(opcode, self.opc)
  122. if "optype" in kwargs:
  123. assert optype == kwargs.pop("has_args")
  124. super().__init(
  125. opcode,
  126. opname,
  127. arg,
  128. argval,
  129. argrepr,
  130. offset,
  131. starts_line,
  132. is_jump_target,
  133. positions,
  134. optype=optype,
  135. has_args=has_args,
  136. **kwargs
  137. )
  138. self.opc = opc
  139. self.Instruction = Instruction
  140. def _print(self, x: str, file=None) -> None:
  141. if file is None:
  142. print(x)
  143. else:
  144. file.write(str(x) + "\n")
  145. def code_info(self, x) -> str:
  146. """Formatted details of methods, functions, or code."""
  147. return _code_info(x, self.python_version_tuple)
  148. def show_code(self, x, file=None) -> None:
  149. """Print details of methods, functions, or code to *file*.
  150. If *file* is not provided, the output is printed on stdout.
  151. """
  152. return _show_code(x, self.opc.version_tuple, file, is_pypy=self.is_pypy)
  153. def stack_effect(self, opcode, oparg: int = 0, jump=None):
  154. """Compute the stack effect of *opcode* with argument *oparg*."""
  155. return _stack_effect(opcode, self.opc, oparg, jump)
  156. def pretty_flags(self, flags) -> str:
  157. """Return pretty representation of code flags."""
  158. return _pretty_flags(flags)
  159. def dis(self, x=None, file=None) -> None:
  160. """Disassemble classes, methods, functions, generators, or code.
  161. With no argument, disassemble the last traceback.
  162. """
  163. self._print(self.Bytecode(x).dis(), file)
  164. def distb(self, tb=None, file=None) -> None:
  165. """Disassemble a traceback (default: last traceback)."""
  166. if tb is None:
  167. try:
  168. tb = sys.last_traceback
  169. except AttributeError:
  170. raise RuntimeError("no last traceback to disassemble")
  171. while tb.tb_next:
  172. tb = tb.tb_next
  173. self.disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file)
  174. def disassemble(self, code, lasti: int=-1, file=None) -> None:
  175. """Disassemble a code object."""
  176. return self.disco(code, lasti, file)
  177. def disco(self, code, lasti=-1, file=None) -> None:
  178. """Disassemble a code object."""
  179. return _disco(
  180. self.python_version_tuple,
  181. code,
  182. timestamp=None,
  183. out=file,
  184. is_pypy=self.is_pypy,
  185. is_graal=self.is_graal,
  186. )
  187. def get_instructions(self, x, first_line=None):
  188. """Iterator for the opcodes in methods, functions or code
  189. Generates a series of Instruction named tuples giving the details of
  190. each operation in the supplied code.
  191. If *first_line* is not None, it indicates the line number that should
  192. be reported for the first source line in the disassembled code.
  193. Otherwise, the source line information (if any) is taken directly from
  194. the disassembled code object.
  195. """
  196. return self.Bytecode(x).get_instructions(x, first_line)
  197. def findlinestarts(self, code):
  198. """Find the offsets in a byte code which are start of lines in the source.
  199. Generate pairs (offset, lineno) as described in Python/compile.c.
  200. """
  201. return self.opc.findlinestarts(code)
  202. def findlabels(self, code):
  203. """Detect all offsets in a byte code which are jump targets.
  204. Return the list of offsets.
  205. """
  206. return self.opc.findlabels(code, self.opc)
  207. def make_std_api(python_version=sys.version_info, variant=VARIANT):
  208. """
  209. Generate an object which can be used in the same way as the Python
  210. standard ``dis`` module.
  211. The difference is that the generated 'module' can be
  212. used to disassemble byte / word code from a different python version than
  213. the version of the interpreter under which we are running.
  214. :param python_version: Generate a dis module for this version of python
  215. (defaults to the currently running python version.)
  216. :param variant: The string denoting the variant of the python version
  217. being run, for example 'pypy' or 'alpha0', 'rc1' etc.,
  218. or None to auto-detect based on the currently running
  219. interpreter.
  220. :return: An Object which can be used like the std ``dis`` module.
  221. """
  222. if isinstance(python_version, float):
  223. major = int(python_version)
  224. minor = int(((python_version - major) + 0.05) * 10)
  225. python_version = (major, minor)
  226. return _StdApi(python_version, variant)
  227. _std_api = make_std_api()
  228. hasconst = _std_api.hasconst
  229. hasname = _std_api.hasname
  230. opmap = _std_api.opmap
  231. opname = _std_api.opname
  232. EXTENDED_ARG = _std_api.EXTENDED_ARG
  233. HAVE_ARGUMENT = _std_api.HAVE_ARGUMENT
  234. xcode = _std_api.xcode
  235. opc = _std_api.opc
  236. Bytecode = _std_api.Bytecode
  237. Instruction = _std_api.Instruction
  238. code_info = _std_api.code_info
  239. _print = _std_api._print
  240. show_code = _std_api.show_code
  241. pretty_flags = _std_api.pretty_flags
  242. dis = _std_api.dis
  243. distb = _std_api.distb
  244. disassemble = _std_api.disassemble
  245. disco = _std_api.disco
  246. get_instructions = _std_api.get_instructions
  247. findlinestarts = _std_api.findlinestarts
  248. findlabels = _std_api.findlabels