diff --git a/pytest/test_cross_dis.py b/pytest/test_cross_dis.py index ba9ba33a..d6c6f442 100644 --- a/pytest/test_cross_dis.py +++ b/pytest/test_cross_dis.py @@ -3,7 +3,7 @@ import pytest from xdis.cross_dis import findlabels from xdis.op_imports import get_opcode_module -from xdis.version_info import IS_GRAAL, PYTHON_VERSION_TRIPLE +from xdis.version_info import IS_GRAAL, PYTHON_IMPLEMENTATION, PYTHON_VERSION_TRIPLE @pytest.mark.skipif(IS_GRAAL, reason="Graal label finding is wonky") @@ -13,7 +13,7 @@ ) def test_findlabels(): code = findlabels.__code__.co_code - opc = get_opcode_module() + opc = get_opcode_module(PYTHON_VERSION_TRIPLE, PYTHON_IMPLEMENTATION) assert findlabels(code, opc) == findlabels_std(code) diff --git a/pytest/test_disasm.py b/pytest/test_disasm.py index 9178435e..2c6098ca 100644 --- a/pytest/test_disasm.py +++ b/pytest/test_disasm.py @@ -98,7 +98,7 @@ def run_check_disasm(test_tuple, function_to_test): [ ("01_fstring", "3.6", ["classic", "xasm"]), # ("01_fstring", "3.10"), # FIXME - ("04_pypy_lambda", "2.7pypy", ["classic", "xasm"]), + ("04_pypy_lambda", "2.7PyPy", ["classic", "xasm"]), ("03_big_dict", "2.7", ["classic", "xasm"]), ("03_big_dict", "3.3", ["classic", "xasm"]), ("03_big_dict", "3.5", ["classic", "xasm"]), diff --git a/pytest/test_stack_effect.py b/pytest/test_stack_effect.py index b3e0b4a5..f387360f 100644 --- a/pytest/test_stack_effect.py +++ b/pytest/test_stack_effect.py @@ -5,6 +5,7 @@ from xdis import get_opcode from xdis.cross_dis import op_has_argument, xstack_effect from xdis.op_imports import get_opcode_module +from xdis.version_info import PYTHON_IMPLEMENTATION, PYTHON_VERSION_TRIPLE def get_srcdir() -> str: @@ -123,11 +124,7 @@ def test_one(xdis_args, dis_args, has_arg: bool) -> None: ) print("%d (%s) is good: effect %d" % (opcode, opname, effect)) - if xdis.IS_PYPY: - variant = "pypy" - else: - variant = "" - opc = get_opcode_module(None, variant) + opc = get_opcode_module(PYTHON_VERSION_TRIPLE, PYTHON_IMPLEMENTATION) for ( opname, opcode, diff --git a/pytest/testdata/04_pypy_lambda-2.7pypy.right b/pytest/testdata/04_pypy_lambda-2.7PyPy.right similarity index 100% rename from pytest/testdata/04_pypy_lambda-2.7pypy.right rename to pytest/testdata/04_pypy_lambda-2.7PyPy.right diff --git a/pytest/testdata/04_pypy_lambda-xasm-2.7pypy.right b/pytest/testdata/04_pypy_lambda-xasm-2.7PyPy.right similarity index 100% rename from pytest/testdata/04_pypy_lambda-xasm-2.7pypy.right rename to pytest/testdata/04_pypy_lambda-xasm-2.7PyPy.right diff --git a/test/bytecode_2.7pypy/01_unicode.pyc b/test/bytecode_2.7PyPy/01_unicode.pyc similarity index 100% rename from test/bytecode_2.7pypy/01_unicode.pyc rename to test/bytecode_2.7PyPy/01_unicode.pyc diff --git a/test/bytecode_2.7pypy/02_complex.pyc b/test/bytecode_2.7PyPy/02_complex.pyc similarity index 100% rename from test/bytecode_2.7pypy/02_complex.pyc rename to test/bytecode_2.7PyPy/02_complex.pyc diff --git a/test/bytecode_2.7pypy/04_pypy_lambda.pyc b/test/bytecode_2.7PyPy/04_pypy_lambda.pyc similarity index 100% rename from test/bytecode_2.7pypy/04_pypy_lambda.pyc rename to test/bytecode_2.7PyPy/04_pypy_lambda.pyc diff --git a/test/bytecode_2.7pypy/05_24_float.pyc b/test/bytecode_2.7PyPy/05_24_float.pyc similarity index 100% rename from test/bytecode_2.7pypy/05_24_float.pyc rename to test/bytecode_2.7PyPy/05_24_float.pyc diff --git a/xdis/__init__.py b/xdis/__init__.py index 56e63dc5..aee58978 100644 --- a/xdis/__init__.py +++ b/xdis/__init__.py @@ -146,6 +146,7 @@ IS_GRAAL, IS_PYPY, PYTHON3, + PYTHON_IMPLEMENTATION, PYTHON_VERSION_STR, PYTHON_VERSION_TRIPLE, ) @@ -284,6 +285,7 @@ "IS_GRAAL", "IS_PYPY", "PYTHON3", + "PYTHON_IMPLEMENTATION", "PYTHON_VERSION_STR", "PYTHON_VERSION_TRIPLE", "__version__", diff --git a/xdis/bytecode.py b/xdis/bytecode.py index 4d0235e4..3dd7c303 100644 --- a/xdis/bytecode.py +++ b/xdis/bytecode.py @@ -40,9 +40,7 @@ from xdis.op_imports import get_opcode_module from xdis.opcodes.opcode_36 import format_CALL_FUNCTION, format_CALL_FUNCTION_EX from xdis.util import code2num, num2code -from xdis.version_info import IS_PYPY - -VARIANT = "pypy" if IS_PYPY else None +from xdis.version_info import PYTHON_IMPLEMENTATION def get_docstring(filename: str, line_number: int, doc_str: str) -> str: @@ -573,7 +571,7 @@ def __repr__(self) -> str: def from_traceback(cls, tb, opc=None): """Construct a Bytecode from the given traceback""" if opc is None: - opc = get_opcode_module(sys.version_info, VARIANT) + opc = get_opcode_module(sys.version_info, PYTHON_IMPLEMENTATION) while tb.tb_next: tb = tb.tb_next return cls( diff --git a/xdis/cross_dis.py b/xdis/cross_dis.py index 0a8447f1..89ebe82a 100644 --- a/xdis/cross_dis.py +++ b/xdis/cross_dis.py @@ -19,7 +19,7 @@ # earlier versions of xdis (and without attribution). from types import CodeType -from typing import List, Optional +from typing import List, Optional, Tuple from xdis.util import ( COMPILER_FLAG_NAMES, @@ -27,7 +27,7 @@ better_repr, code2num, ) -from xdis.version_info import IS_GRAAL +from xdis.version_info import IS_GRAAL, PYTHON_IMPLEMENTATION, PythonImplementation def _try_compile(source: str, name: str) -> CodeType: @@ -44,9 +44,13 @@ def _try_compile(source: str, name: str) -> CodeType: return c -def code_info(x, version_tuple, is_pypy=False) -> str: +def code_info( + x, version_tuple: Tuple[int, ...], python_implementation: PythonImplementation +) -> str: """Formatted details of methods, functions, or code.""" - return format_code_info(get_code_object(x), version_tuple, is_pypy=is_pypy) + return format_code_info( + get_code_object(x), version_tuple, python_implementation=python_implementation + ) def get_code_object(x): @@ -96,9 +100,11 @@ def get_cache_size_313(opname: str) -> int: } return _inline_cache_entries.get(opname, 0) + # For compatibility _get_cache_size_313 = get_cache_size_313 + def findlabels(code: bytes, opc): if opc.version_tuple < (3, 10) or IS_GRAAL: return findlabels_pre_310(code, opc) @@ -114,7 +120,10 @@ def findlabels_310(code: bytes, opc): for offset, op, arg in unpack_opargs_bytecode_310(code, opc): if arg is not None: if op in opc.JREL_OPS: - if opc.version_tuple >= (3, 11) and opc.opname[op] in ("JUMP_BACKWARD", "JUMP_BACKWARD_NO_INTERRUPT"): + if opc.version_tuple >= (3, 11) and opc.opname[op] in ( + "JUMP_BACKWARD", + "JUMP_BACKWARD_NO_INTERRUPT", + ): arg = -arg label = offset + 2 + arg * 2 # in 3.13 we have to add total cache offsets to label @@ -155,7 +164,7 @@ def findlabels_pre_310(code, opc): NO_LINE_NUMBER = -128 -def findlinestarts(code, dup_lines: bool=False): +def findlinestarts(code, dup_lines: bool = False): """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. @@ -234,15 +243,20 @@ def instruction_size(op, opc) -> int: op_size = instruction_size -def show_code(co, version_tuple, file=None, is_pypy: bool=False) -> None: +def show_code( + co, + version_tuple: Tuple[int, ...], + file=None, + python_implementation=PYTHON_IMPLEMENTATION, +) -> None: """Print details of methods, functions, or code to *file*. If *file* is not provided, the output is printed on stdout. """ if file is None: - print(code_info(co, version_tuple, is_pypy=is_pypy)) + print(code_info(co, version_tuple, python_implementation)) else: - file.write(code_info(co, version_tuple) + "\n") + file.write(code_info(co, version_tuple, python_implementation) + "\n") def op_has_argument(opcode: int, opc) -> bool: @@ -252,7 +266,7 @@ def op_has_argument(opcode: int, opc) -> bool: return opcode >= opc.HAVE_ARGUMENT -def pretty_flags(flags, is_pypy=False) -> str: +def pretty_flags(flags, python_implementation=PYTHON_IMPLEMENTATION) -> str: """Return pretty representation of code flags.""" names = [] result = "0x%08x" % flags @@ -260,7 +274,7 @@ def pretty_flags(flags, is_pypy=False) -> str: flag = 1 << i if flags & flag: names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag))) - if is_pypy: + if python_implementation == PythonImplementation.PyPy: names.append(PYPY_COMPILER_FLAG_NAMES.get(flag, hex(flag))) flags ^= flag if not flags: @@ -272,7 +286,11 @@ def pretty_flags(flags, is_pypy=False) -> str: def format_code_info( - co, version_tuple: tuple, name=None, is_pypy=False, is_graal=False, file_offset: Optional[tuple]=None + co, + version_tuple: tuple, + name=None, + python_implementation=PYTHON_IMPLEMENTATION, + file_offset: Optional[tuple] = None, ) -> str: if not name: name = co.co_name @@ -306,7 +324,7 @@ def format_code_info( if version_tuple >= (1, 3): lines.append( - "# Flags: %s" % pretty_flags(co.co_flags, is_pypy=is_pypy) + "# Flags: %s" % pretty_flags(co.co_flags, python_implementation) ) if version_tuple >= (1, 5): diff --git a/xdis/disasm.py b/xdis/disasm.py index c65da755..f42ade94 100644 --- a/xdis/disasm.py +++ b/xdis/disasm.py @@ -37,24 +37,28 @@ from xdis.codetype.base import iscode from xdis.cross_dis import format_code_info, format_exception_table from xdis.load import check_object_path, load_module -from xdis.magics import GRAAL3_MAGICS, PYTHON_MAGIC_INT +from xdis.magics import PYTHON_MAGIC_INT from xdis.op_imports import op_imports, remap_opcodes from xdis.version import __version__ -from xdis.version_info import IS_PYPY, PYTHON_VERSION_TRIPLE +from xdis.version_info import ( + PYTHON_IMPLEMENTATION, + PYTHON_VERSION_TRIPLE, + PythonImplementation, +) -def get_opcode(version_tuple, is_pypy, alternate_opmap=None): +def get_opcode(version_tuple, python_implementation, alternate_opmap=None): # Set up disassembler with the right opcodes lookup = ".".join((str(i) for i in version_tuple)) - if is_pypy: - lookup += "pypy" + if python_implementation == PythonImplementation.PyPy: + lookup += "PyPy" if lookup in op_imports.keys(): if alternate_opmap is not None: # TODO: change bytecode version number comment line to indicate altered return remap_opcodes(op_imports[lookup], alternate_opmap) return op_imports[lookup] - if is_pypy: - pypy_str = " for pypy" + if python_implementation != PythonImplementation.CPyton: + pypy_str = f" for {python_implementation}" else: pypy_str = "" raise TypeError(f"{lookup} is not a Python version{pypy_str} I know about") @@ -65,28 +69,17 @@ def show_module_header( co, timestamp, out=sys.stdout, - is_pypy=False, magic_int=None, source_size=None, sip_hash=None, header=True, show_filename=True, - is_graal=False, + python_implementation: PythonImplementation = PYTHON_IMPLEMENTATION, file_offset: Optional[int] = None, ) -> None: bytecode_version = ".".join((str(i) for i in version_tuple)) real_out = out or sys.stdout - if is_pypy: - co_pypy_str = "PyPy " - elif is_graal: - co_pypy_str = "Graal " - else: - co_pypy_str = "" - - if IS_PYPY: - run_pypy_str = "PyPy " - else: - run_pypy_str = "" + implementation_str = python_implementation if header: magic_str = "" @@ -94,15 +87,15 @@ def show_module_header( magic_str = str(magic_int) real_out.write( ( - "# pydisasm version %s\n# %sPython bytecode %s%s" - "\n# Disassembled from %sPython %s\n" + "# pydisasm version %s\n# %s Python bytecode %s%s" + "\n# Disassembled from %s Python %s\n" ) % ( __version__, - co_pypy_str, + implementation_str, bytecode_version, " (%s)" % magic_str, - run_pypy_str, + str(PYTHON_IMPLEMENTATION), "\n# ".join(sys.version.split("\n")), ) ) @@ -131,15 +124,14 @@ def disco( co, timestamp, out=sys.stdout, - is_pypy: bool = False, magic_int=None, source_size=None, sip_hash=None, asm_format: str = "classic", alternate_opmap=None, show_source: bool = False, - is_graal: bool = False, methods=tuple(), + python_implementation=PYTHON_IMPLEMENTATION, file_offsets: dict = {}, ) -> None: """ @@ -153,14 +145,13 @@ def disco( co, timestamp, out, - is_pypy, magic_int, source_size, sip_hash, header=True, show_filename=False, - is_graal=is_graal, - ) + python_implementation=python_implementation) + # Store final output stream when there is an error. real_out = out or sys.stdout @@ -171,14 +162,14 @@ def disco( format_code_info( co, version_tuple, - is_graal=is_graal, - file_offset=file_offsets.get(co) if not is_graal else {}, + python_implementation=python_implementation, + file_offset=file_offsets.get(co), ) + "\n" ) pass - opc = get_opcode(version_tuple, is_pypy, alternate_opmap) + opc = get_opcode(version_tuple, python_implementation, alternate_opmap) if asm_format == "xasm": disco_loop_asm_format(opc, version_tuple, co, real_out, {}, set([])) @@ -194,7 +185,7 @@ def disco( show_source=show_source, methods=methods, file_offsets=file_offsets, - is_unusual_bytecode=is_graal, + is_unusual_bytecode=python_implementation == PythonImplementation.Graal, ) @@ -246,9 +237,13 @@ def disco_loop( else: if co.co_name == "??": - real_out.write("\n# Instruction disassembly not supported here.\n") + real_out.write( + "\n# Instruction disassembly not supported here.\n" + ) else: - real_out.write(f"\n# Instruction disassembly for {co.co_name} not supported here.\n") + real_out.write( + f"\n# Instruction disassembly for {co.co_name} not supported here.\n" + ) real_out.write(f"instruction bytecode:\n{co.co_code.hex(':')}\n") else: @@ -372,14 +367,13 @@ def disassemble_file( pyc_filename = None file_offsets = {} try: - # FIXME: add whether we want PyPy pyc_filename = check_object_path(filename) ( version_tuple, timestamp, magic_int, co, - is_pypy, + python_implementation, source_size, sip_hash, file_offsets, @@ -394,7 +388,7 @@ def disassemble_file( stat = os.stat(filename) source = open(filename, "r").read() co = compile(source, filename, "exec") - is_pypy = IS_PYPY + python_implementation = PYTHON_IMPLEMENTATION magic_int = PYTHON_MAGIC_INT sip_hash = 0 source_size = stat.st_size @@ -403,21 +397,18 @@ def disassemble_file( else: filename = pyc_filename - is_graal = magic_int in GRAAL3_MAGICS - if asm_format == "header": show_module_header( version_tuple, co, timestamp, outstream, - is_pypy, magic_int, source_size, sip_hash, header=True, show_filename=True, - is_graal=is_graal, + python_implementation=python_implementation, ) else: disco( @@ -425,14 +416,13 @@ def disassemble_file( co=co, timestamp=timestamp, out=outstream, - is_pypy=is_pypy, + python_implementation=python_implementation, magic_int=magic_int, source_size=source_size, sip_hash=sip_hash, asm_format=asm_format, alternate_opmap=alternate_opmap, show_source=show_source, - is_graal=is_graal, methods=methods, file_offsets=file_offsets, ) @@ -443,7 +433,7 @@ def disassemble_file( version_tuple, timestamp, magic_int, - is_pypy, + python_implementation, source_size, sip_hash, ) diff --git a/xdis/lineoffsets.py b/xdis/lineoffsets.py index 353c4eaa..e267cb74 100644 --- a/xdis/lineoffsets.py +++ b/xdis/lineoffsets.py @@ -112,14 +112,10 @@ def line_numbers(self, include_dups: bool=True, include_offsets: bool=False): def lineoffsets_in_file(filename: str, toplevel_only=False) -> LineOffsetInfo | None: obj_path = check_object_path(filename) - version, timestamp, magic_int, code, pypy, source_size, sip_hash, _save_offsets = load_module( + version, timestamp, magic_int, code, python_implementation, source_size, sip_hash, _save_offsets = load_module( obj_path ) - if pypy: - variant = "pypy" - else: - variant = None - opc = get_opcode_module(version, variant) + opc = get_opcode_module(version, python_implementation) return LineOffsetInfo(opc, code, not toplevel_only) pass @@ -129,6 +125,8 @@ def lineoffsets_in_module(module, toplevel_only: bool=False) -> LineOffsetInfo | if __name__ == "__main__": + from xdis.version_info import PYTHON_IMPLEMENTATION, PYTHON_VERSION_TRIPLE + def multi_line() -> tuple[int, int]: # We have two statements on the same line @@ -176,6 +174,6 @@ def print_code_info(code_info: LineOffsetInfo | None) -> None: pass return - opc = get_opcode_module() + opc = get_opcode_module(PYTHON_VERSION_TRIPLE, PYTHON_IMPLEMENTATION) print_code_info(lineoffsets_in_file(__file__)) # print_code_info(LineOffsetInfo(opc, multi_line.__code__, include_children=True)) diff --git a/xdis/load.py b/xdis/load.py index 02360978..70dfdcfd 100644 --- a/xdis/load.py +++ b/xdis/load.py @@ -41,7 +41,7 @@ py_str2tuple, versions, ) -from xdis.version_info import PYTHON3, PYTHON_VERSION_TRIPLE +from xdis.version_info import PYTHON3, PYTHON_VERSION_TRIPLE, PythonImplementation def is_python_source(path) -> bool: @@ -176,7 +176,7 @@ def load_module( magic_int: int, a bytecode-specific version number. This is related to the Python version number, the two aren't quite the same thing. co : code object - ispypy : True if this was a PyPy code object + python_implementation : The variant of Python the bytecode is written in, e.g. CPython, Graal, Rust, Jython, ... source_size: The size of the source code mod 2**32, if that was stored in the bytecode. None otherwise. sip_hash : the SIP Hash for the file (only in Python 3.7 or greater), if the file @@ -224,6 +224,7 @@ def load_module_from_file_object( timestamp = 0 file_offsets = {} + python_implementation = PythonImplementation.CPython try: magic = fp.read(4) magic_int = magic2int(magic) @@ -318,7 +319,11 @@ def load_module_from_file_object( # the graal python interpreter to crash! # For these reasons, we are better off using our marshal routines # for Graal Python. - is_graal = magic_int in GRAAL3_MAGICS + if magic_int in GRAAL3_MAGICS: + is_graal = True + python_implementation = PythonImplementation.Graal + else: + is_graal = False if save_file_offsets and not is_graal: co, file_offsets = xdis.unmarshal.load_code_and_get_file_offsets( fp, magic_int, code_objects @@ -350,12 +355,15 @@ def load_module_from_file_object( finally: fp.close() + if is_pypy(magic_int, filename): + python_implementation = PythonImplementation.PyPy + return ( version_triple, timestamp, magic_int, co, - is_pypy(magic_int, filename), + python_implementation, source_size, sip_hash, file_offsets, diff --git a/xdis/magics.py b/xdis/magics.py index 0684477b..bf7a8e89 100755 --- a/xdis/magics.py +++ b/xdis/magics.py @@ -222,10 +222,10 @@ def __by_version(magic_versions: Dict[bytes, str]) -> dict: add_magic_from_int(62201, "2.7a0+3") # introduce BUILD_SET add_magic_from_int(62211, "2.7") # introduce MAP_ADD and SET_ADD -add_magic_from_int(2657, "2.7pyston-0.6.1") +add_magic_from_int(2657, "2.7Pyston-0.6.1") # PyPy including pypy-2.6.1, pypy-5.0.1 PyPy adds 7 to the corresponding CPython number -add_magic_from_int(62211 + 7, "2.7pypy") +add_magic_from_int(62211 + 7, "2.7PyPy") add_magic_from_int(3000, "3.000") add_magic_from_int(3010, "3.000+1") # removed UNARY_CONVERT @@ -250,7 +250,7 @@ def __by_version(magic_versions: Dict[bytes, str]) -> dict: # Python 3.2.5 - PyPy 2.3.4 PyPy adds 7 to the corresponding CPython # number -add_magic_from_int(3180 + 7, "3.2pypy") +add_magic_from_int(3180 + 7, "3.2PyPy") add_magic_from_int(3190, "3.3a0") # __class__ super closure changed add_magic_from_int(3200, "3.3a0+") # __qualname__ added @@ -681,16 +681,16 @@ def __by_version(magic_versions: Dict[bytes, str]) -> dict: # Often, PyPY increases its magic number by 16. add_magic_from_int(48, "3.2a2") -add_magic_from_int(64, "3.3pypy") -add_magic_from_int(112, "3.5pypy") # pypy3.5-c-jit-latest -add_magic_from_int(160, "3.6.1pypy") # '3.6.1 ... PyPy 7.1.0-beta0' -add_magic_from_int(192, "3.6pypy") # '3.6.9 ... PyPy 7.1.0-beta0' -add_magic_from_int(224, "3.7pypy") # PyPy 3.7.9-beta0 -add_magic_from_int(240, "3.7pypy") # PyPy 3.7.9-beta0 -add_magic_from_int(256, "3.8pypy") # PyPy 3.8.15 -add_magic_from_int(320, "3.9pypy") # PyPy 3.9-v7.3.8 -add_magic_from_int(336, "3.9pypy") # PyPy 3.9.15, PyPy 3.9.17 -add_magic_from_int(384, "3.10pypy") # PyPy 3.10.12 +add_magic_from_int(64, "3.3PyPy") +add_magic_from_int(112, "3.5PyPy") # pypy3.5-c-jit-latest +add_magic_from_int(160, "3.6.1pyPy") # '3.6.1 ... PyPy 7.1.0-beta0' +add_magic_from_int(192, "3.6PyPy") # '3.6.9 ... PyPy 7.1.0-beta0' +add_magic_from_int(224, "3.7PyPy") # PyPy 3.7.9-beta0 +add_magic_from_int(240, "3.7PyPy") # PyPy 3.7.9-beta0 +add_magic_from_int(256, "3.8PyPy") # PyPy 3.8.15 +add_magic_from_int(320, "3.9PyPy") # PyPy 3.9-v7.3.8 +add_magic_from_int(336, "3.9PyPy") # PyPy 3.9.15, PyPy 3.9.17 +add_magic_from_int(384, "3.10PyPy") # PyPy 3.10.12 add_magic_from_int(416, "3.11.13pypy") # PyPy 3.11.13 or pypy3.11-7.3.20 add_magic_from_int(12641, "3.12.0a.rust") # RustPython 3.12.0 @@ -726,9 +726,9 @@ def __by_version(magic_versions: Dict[bytes, str]) -> dict: add_magic_from_int(22138, "2.7.7Pyston") # 2.7.8pyston, pyston-0.6.0 magics = __by_version(versions) -magics["3.8.12pypy"] = magics["3.8.0rc1+"] -magics["3.9.15pypy"] = magics["3.9.0alpha1"] -magics["3.9.16pypy"] = magics["3.9.0alpha1"] +magics["3.8.12PyPy"] = magics["3.8.0rc1+"] +magics["3.9.15PyPy"] = magics["3.9.0alpha1"] +magics["3.9.16PyPy"] = magics["3.9.0alpha1"] # From a Python version given in sys.info, e.g. 3.6.1, # what is the "canonic" version number, e.g. '3.6.0rc1' @@ -779,20 +779,20 @@ def add_canonic_versions(release_versions: str, canonic: str) -> None: add_canonic_versions("3.7b1", "3.7.0beta3") add_canonic_versions("3.8a1", "3.8.0beta2") -add_canonic_versions("2.7.10pypy 2.7.12pypy 2.7.13pypy 2.7.18pypy", "2.7pypy") +add_canonic_versions("2.7.10PyPy 2.7.12PyPy 2.7.13PyPy 2.7.18PyPy", "2.7PyPy") add_canonic_versions("2.7.3b0Jython", "2.7.1b3Jython") add_canonic_versions("3.8.5Graal", "3.8.5Graal (16)") add_canonic_versions("3.8.10Graal", "3.8.0rc1+") -add_canonic_versions("3.2.5pypy", "3.2pypy") -add_canonic_versions("3.3.5pypy", "3.3pypy") -add_canonic_versions("3.5.3pypy", "3.5pypy") -add_canonic_versions("3.6.9pypy 3.6.12pypy", "3.6pypy") -add_canonic_versions("3.7.0pypy 3.7.9pypy 3.7.10pypy 3.7.12pypy 3.7.13pypy", "3.7pypy") -add_canonic_versions("3.8.0pypy 3.8.12pypy 3.8.13pypy 3.8.15pypy", "3.8.12pypy") -add_canonic_versions("3.8.16pypy", "3.8pypy") -add_canonic_versions("3.9.17pypy 3.9.18pypy 3.9.19pypy", "3.9pypy") -add_canonic_versions("3.10.12pypy 3.10.13pypy 3.10.14pypy 3.10pypy", "3.10pypy") +add_canonic_versions("3.2.5PyPy", "3.2PyPy") +add_canonic_versions("3.3.5PyPy", "3.3PyPy") +add_canonic_versions("3.5.3PyPy", "3.5PyPy") +add_canonic_versions("3.6.9PyPy 3.6.12PyPy", "3.6PyPy") +add_canonic_versions("3.7.0PyPy 3.7.9PyPy 3.7.10PyPy 3.7.12PyPy 3.7.13PyPy", "3.7PyPy") +add_canonic_versions("3.8.0PyPy 3.8.12PyPy 3.8.13PyPy 3.8.15PyPy", "3.8.12PyPy") +add_canonic_versions("3.8.16PyPy", "3.8PyPy") +add_canonic_versions("3.9.17PyPy 3.9.18PyPy 3.9.19PyPy", "3.9PyPy") +add_canonic_versions("3.10.12PyPy 3.10.13PyPy 3.10.14PyPy 3.10PyPy", "3.10PyPy") add_canonic_versions("2.7.8Pyston", "2.7.7Pyston") add_canonic_versions("3.7.0alpha3", "3.7.0alpha3") add_canonic_versions( @@ -811,8 +811,8 @@ def add_canonic_versions(release_versions: str, canonic: str) -> None: ) add_canonic_versions( "3.9 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 3.9.10 3.9.11 " - "3.9.12 3.9.13 3.9.14 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.10pypy 3.9.11pypy 3.9.12pypy " - "3.9.15pypy 3.9.16pypy 3.9.0b5+ 3.9.17 3.9.18 3.9.19 3.9.20 3.9.21 3.9.22 3.9.23 3.9.24", + "3.9.12 3.9.13 3.9.14 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.10PyPy 3.9.11PyPy 3.9.12PyPy " + "3.9.15PyPy 3.9.16PyPy 3.9.0b5+ 3.9.17 3.9.18 3.9.19 3.9.20 3.9.21 3.9.22 3.9.23 3.9.24", "3.9.0beta5", ) @@ -854,10 +854,10 @@ def add_canonic_versions(release_versions: str, canonic: str) -> None: # A set of all Python versions we know about python_versions = set(canonic_python_version.keys()) -# Python major, minor version names, e.g. 3.6, 3.11pypy, etc. +# Python major, minor version names, e.g. 3.6, 3.11PyPy, etc. # These are not considered interim version number. minor_release_names = { - python_version for python_version in python_versions if re.match("^[1-3][.][0-9]+(?:pypy|Graal)?$", python_version) + python_version for python_version in python_versions if re.match("^[1-3][.][0-9]+(?:PyPy|Graal)?$", python_version) } @@ -886,7 +886,7 @@ def py_str2tuple(orig_version: str) -> Tuple[int, int] | Tuple[int, int, int]: tuple. For example 3.2a1, 3.2.0, 3.2.2, 3.2.6 among others all map to (3, 2). """ - version = re.sub(r"(pypy|dropbox)$", "", orig_version) + version = re.sub(r"(PyPy|dropbox)$", "", orig_version) if version in magics: m = re.match(r"^(\d)\.(\d+)\.(\d+)", version) if m: @@ -914,7 +914,7 @@ def sysinfo2magic(version_info: tuple = tuple(sys.version_info)) -> bytes: vers_str += version_tuple_to_str(version_info, start=3) if IS_PYPY: - vers_str += "pypy" + vers_str += "PyPy" elif IS_GRAAL: vers_str += "Graal" elif IS_RUST: diff --git a/xdis/op_imports.py b/xdis/op_imports.py index 46c6774b..79e7a4b7 100644 --- a/xdis/op_imports.py +++ b/xdis/op_imports.py @@ -16,7 +16,7 @@ """Facilitates for importing Python opcode maps for a given Python version""" import copy -import sys +from typing import Tuple from xdis.magics import canonic_python_version from xdis.opcodes import ( @@ -62,7 +62,7 @@ opcode_313, opcode_314, ) -from xdis.version_info import IS_PYPY, version_tuple_to_str +from xdis.version_info import PythonImplementation, version_tuple_to_str # FIXME op_imports = { @@ -94,13 +94,13 @@ 2.5: opcode_25, "2.5.0dropbox": opcode_25, "2.6a1": opcode_26, - "2.6pypy": opcode_26pypy, + "2.6PyPy": opcode_26pypy, 2.6: opcode_26, "2.7": opcode_27, 2.7: opcode_27, "2.7.18candidate1": opcode_27, - "2.7pypy": opcode_27pypy, - "2.7.12pypy": opcode_27pypy, + "2.7PyPy": opcode_27pypy, + "2.7.12PyPy": opcode_27pypy, "3.0": opcode_30, 3.0: opcode_30, "3.0a5": opcode_30, @@ -110,15 +110,15 @@ "3.2": opcode_32, "3.2a2": opcode_32, 3.2: opcode_32, - "3.2pypy": opcode_32pypy, + "3.2PyPy": opcode_32pypy, "3.3a4": opcode_33, 3.3: opcode_33, - "3.3pypy": opcode_33pypy, + "3.3PyPy": opcode_33pypy, "3.4": opcode_34, "3.4rc2": opcode_34, 3.4: opcode_34, "3.5": opcode_35, - "3.5pypy": opcode_35pypy, + "3.5PyPy": opcode_35pypy, "3.5.1": opcode_35, "3.5.2": opcode_35, "3.5.3": opcode_35, @@ -126,12 +126,12 @@ 3.5: opcode_35, "3.6rc1": opcode_36, 3.6: opcode_36, - "3.6pypy": opcode_36pypy, - "3.6.1pypy": opcode_36pypy, + "3.6PyPy": opcode_36pypy, + "3.6.1PyPy": opcode_36pypy, "3.7.0beta3": opcode_37, "3.7.0.beta3": opcode_37, "3.7.0": opcode_37, - "3.7pypy": opcode_37pypy, + "3.7PyPy": opcode_37pypy, 3.7: opcode_37, "3.8.0alpha0": opcode_38, "3.8.0a0": opcode_38, @@ -141,29 +141,29 @@ "3.8.0rc1+": opcode_38, "3.8.0candidate1": opcode_38, "3.8": opcode_38, - "3.8pypy": opcode_38pypy, - "3.8.0pypy": opcode_38pypy, - "3.8.12pypy": opcode_38pypy, - "3.8.13pypy": opcode_38pypy, - "3.8.14pypy": opcode_38pypy, - "3.8.15pypy": opcode_38pypy, - "3.8.16pypy": opcode_38pypy, - "3.8.17pypy": opcode_38pypy, + "3.8PyPy": opcode_38pypy, + "3.8.0PyPy": opcode_38pypy, + "3.8.12PyPy": opcode_38pypy, + "3.8.13PyPy": opcode_38pypy, + "3.8.14PyPy": opcode_38pypy, + "3.8.15PyPy": opcode_38pypy, + "3.8.16PyPy": opcode_38pypy, + "3.8.17PyPy": opcode_38pypy, "3.9.0alpha1": opcode_39, "3.9.0alpha2": opcode_39, "3.9.0beta5": opcode_39, "3.9": opcode_39, - "3.9pypy": opcode_39pypy, - "3.9.15pypy": opcode_39pypy, - "3.9.16pypy": opcode_39pypy, - "3.9.17pypy": opcode_39pypy, - "3.9.18pypy": opcode_39pypy, + "3.9PyPy": opcode_39pypy, + "3.9.15PyPy": opcode_39pypy, + "3.9.16PyPy": opcode_39pypy, + "3.9.17PyPy": opcode_39pypy, + "3.9.18PyPy": opcode_39pypy, 3.9: opcode_39, "3.10.0rc2": opcode_310, "3.10.b1": opcode_310, "3.10": opcode_310, - "3.10pypy": opcode_310pypy, - "3.10.12pypy": opcode_310pypy, + "3.10PyPy": opcode_310pypy, + "3.10.12PyPy": opcode_310pypy, "3.11": opcode_311, "3.11.0": opcode_311, "3.11.1": opcode_311, @@ -172,7 +172,7 @@ "3.11.4": opcode_311, "3.11.5": opcode_311, "3.11a7e": opcode_311, - "3.11.13pypy": opcode_311pypy, + "3.11.13PyPy": opcode_311pypy, 3.11: opcode_311, "3.12.0rc2": opcode_312, "3.12.0": opcode_312, @@ -189,17 +189,7 @@ op_imports[k] = op_imports[v] -def get_opcode_module(version_info=None, variant=None): - if version_info is None: - version_info = sys.version_info - if variant is None and IS_PYPY: - variant = "pypy" - pass - pass - elif isinstance(version_info, float): - int_vers = int(version_info * 10) - version_info = [int_vers // 10, int_vers % 10] - +def get_opcode_module(version_info: Tuple[int, ...], implementation: PythonImplementation): vers_str = version_tuple_to_str(version_info) if len(version_info) > 3 and version_info[3] != "final": vers_str += version_tuple_to_str(version_info, start=3) @@ -207,20 +197,8 @@ def get_opcode_module(version_info=None, variant=None): if vers_str not in canonic_python_version: vers_str = version_tuple_to_str(version_info[:2]) - if variant is None: - try: - import platform - - variant = platform.python_implementation() - if platform in ("Jython", "Pyston"): - vers_str += variant - pass - except ImportError: - # Python may be too old, e.g. < 2.6 or implementation may - # just not have the ``platform`` attribute. - pass - elif variant != "Graal": - vers_str += variant + if implementation != PythonImplementation.CPython: + vers_str += str(implementation) return op_imports[canonic_python_version[vers_str]] @@ -337,4 +315,5 @@ def remap_opcodes(op_obj, alternate_opmap): if __name__ == "__main__": - print(get_opcode_module()) + from version_info import PYTHON_IMPLEMENTATION, PYTHON_VERSION_TRIPLE + print(get_opcode_module(PYTHON_VERSION_TRIPLE, PYTHON_IMPLEMENTATION)) diff --git a/xdis/std.py b/xdis/std.py index 1230b317..f6ae1613 100644 --- a/xdis/std.py +++ b/xdis/std.py @@ -49,8 +49,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, @@ -63,24 +61,25 @@ 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 +# xdis +from xdis.version_info import PYTHON_IMPLEMENTATION class _StdApi: - def __init__(self, python_version=sys.version_info, variant=VARIANT) -> None: + def __init__( + self, + python_version=sys.version_info, + python_implementation=PYTHON_IMPLEMENTATION, + ) -> 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.opc = opc = get_opcode_module(python_version, python_implementation) self.python_version_tuple = opc.version_tuple - self.is_pypy = variant == PYPY - self.is_graal = variant == GRAAL + self.python_implementation = python_implementation self.hasconst = opc.hasconst self.hasname = opc.hasname self.opmap = opc.opmap @@ -97,7 +96,9 @@ class Bytecode(_Bytecode): Iterating over these yields a bytecode operation as Instruction instances. """ - def __init__(self, x, first_line=None, current_offset=None, opc=None) -> None: + def __init__( + self, x, first_line=None, current_offset=None, opc=None + ) -> None: if opc is None: opc = _std_api.opc _Bytecode.__init__( @@ -173,14 +174,14 @@ def _print(self, x: str, file=None) -> None: def code_info(self, x) -> str: """Formatted details of methods, functions, or code.""" - return _code_info(x, self.python_version_tuple) + return _code_info(x, self.python_version_tuple, self.python_implementation) 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) + return _show_code(x, self.opc.version_tuple, file, python_implementation=self.python_implementation) def stack_effect(self, opcode, oparg: int = 0, jump=None): """Compute the stack effect of *opcode* with argument *oparg*.""" @@ -209,7 +210,7 @@ def distb(self, tb=None, file=None) -> None: 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: + def disassemble(self, code, lasti: int = -1, file=None) -> None: """Disassemble a code object.""" return self.disco(code, lasti, file) @@ -220,8 +221,7 @@ def disco(self, code, lasti=-1, file=None) -> None: code, timestamp=None, out=file, - is_pypy=self.is_pypy, - is_graal=self.is_graal, + python_implementation=self.python_version_tuple, ) def get_instructions(self, x, first_line=None): @@ -253,7 +253,7 @@ def findlabels(self, code): return self.opc.findlabels(code, self.opc) -def make_std_api(python_version=sys.version_info, variant=VARIANT): +def make_std_api(python_version=sys.version_info, python_implementation=PYTHON_IMPLEMENTATION): """ Generate an object which can be used in the same way as the Python standard ``dis`` module. @@ -275,7 +275,7 @@ def make_std_api(python_version=sys.version_info, variant=VARIANT): major = int(python_version) minor = int(((python_version - major) + 0.05) * 10) python_version = (major, minor) - return _StdApi(python_version, variant) + return _StdApi(python_version, python_implementation) _std_api = make_std_api() diff --git a/xdis/version.py b/xdis/version.py index f1792146..f27592c9 100644 --- a/xdis/version.py +++ b/xdis/version.py @@ -4,4 +4,4 @@ # well as importing into Python. That's why there is no # space around "=" below. # fmt: off -__version__="6.1.9.dev0" # noqa +__version__="6.3.0.dev0" # noqa diff --git a/xdis/version_info.py b/xdis/version_info.py index 62b78fc1..289da97c 100644 --- a/xdis/version_info.py +++ b/xdis/version_info.py @@ -1,23 +1,24 @@ """ - Copyright (c) 2020-2025 by Rocky Bernstein +Copyright (c) 2020-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 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. +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. +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. """ import platform import sys +from enum import Enum from typing import Tuple PYTHON3 = sys.version_info >= (3, 0) @@ -25,13 +26,62 @@ PYTHON_VERSION_TRIPLE = tuple(sys.version_info[:3]) PYTHON_VERSION_STR = "%s.%s" % (sys.version_info[0], sys.version_info[1]) -IS_PYPY = "__pypy__" in sys.builtin_module_names -IS_GRAAL = "Graal" in platform.python_implementation() -IS_RUST = "RustPython" in platform.python_implementation() + +class PythonImplementation(Enum): + """ + Enumeration of Python interpreter implementations. Each member's value is the + canonical string returned by platform.python_implementation() for that implementation. + """ + + CPython = "CPython" + PyPy = "PyPy" + Graal = "Graal" + RustPython = "RustPython" + Jython = "Jython" + Other = "Other" + + def __str__(self) -> str: + """ + Return the string value of the implementation. This makes str(PythonImplemtation.*) + return the underlying implementation string (e.g. "CPython", "Graal", ...). + """ + return self.value + + +def get_python_implementation( + implementation: str = platform.python_implementation(), +) -> PythonImplementation: + """ + Detect the current Python implementation and return the corresponding + PlatformImplemtation enum member. + """ + # Match common exact names first + if implementation == "CPython": + return PythonImplementation.CPython + elif implementation == "PyPy": + return PythonImplementation.PyPy + elif implementation == "RustPython": + return PythonImplementation.RustPython + elif implementation == "Jython": + return PythonImplementation.Jython + # Graal may appear as "GraalVM" or include "Graal" in some environments + elif "Graal" in implementation: + return PythonImplementation.Graal + # If nothing matched, return Other. + return PythonImplementation.Other + + +PYTHON_IMPLEMENTATION = get_python_implementation() +IS_GRAAL = PYTHON_IMPLEMENTATION == PythonImplementation.Graal +IS_PYPY = PYTHON_IMPLEMENTATION == PythonImplementation.PyPy +IS_RUST = PYTHON_IMPLEMENTATION == PythonImplementation.RustPython def version_tuple_to_str( - version_tuple: Tuple[int, ...]=PYTHON_VERSION_TRIPLE, start: int=0, end: int=3, delimiter: str="." + version_tuple: Tuple[int, ...] = PYTHON_VERSION_TRIPLE, + start: int = 0, + end: int = 3, + delimiter: str = ".", ) -> str: """ Turn a version tuple, e.g. (3,2,6), into a dotted string, e.g. "3.2.6". @@ -47,5 +97,5 @@ def version_tuple_to_str( return delimiter.join([str(v) for v in version_tuple[start:end]]) -def version_str_to_tuple(python_version: str, length: int=2) -> tuple: +def version_str_to_tuple(python_version: str, length: int = 2) -> tuple: return tuple([int(v) for v in python_version.split(".")[:length]])