diff --git a/test/bytecode_graal310/02_while1else.graalpy310.pyc b/test/bytecode_graal310/02_while1else.graalpy310.pyc deleted file mode 100644 index abf9f3f5..00000000 Binary files a/test/bytecode_graal310/02_while1else.graalpy310.pyc and /dev/null differ diff --git a/xdis/codetype/code313rust.py b/xdis/codetype/code313rust.py new file mode 100644 index 00000000..47426abe --- /dev/null +++ b/xdis/codetype/code313rust.py @@ -0,0 +1,91 @@ +# (C) Copyright 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. + +from dataclasses import dataclass +from typing import Any, Dict, Tuple, Union + +from xdis.codetype.base import CodeBase + + +@dataclass +class SourceLocation: + # line: 1-based int + line: int + # character_offset: 1-based int (constructed from a zero-indexed stored value) + character_offset: int + + +class Code313Rust(CodeBase): + """Class for a RustPython 3.13 code object used when a Python + interpreter is not RustPython 3.13 but working on RustPython 3.13 bytecode. It + also functions as an object that can be used to build or write a + Python3 code object, since we allow mutable structures. + + When done mutating, call method to_native(). + + For convenience in generating code objects, fields like + `co_consts`, co_names which are (immutable) tuples in the end-result can be stored + instead as (mutable) lists. Likewise, the line number table `co_lnotab` + can be stored as a simple list of offset, line_number tuples. + + """ + def __init__( + self, + co_argcount: int, + co_posonlyargcount: int, + co_kwonlyargcount: int, + co_nlocals: int, + co_stacksize: int, + co_flags: int, + co_code: bytes, + co_consts: tuple, + co_names: tuple[str], + co_varnames: tuple[str], + co_filename: str, + co_name: str, + co_qualname: str, + co_firstlineno: int, + co_linetable: bytes, + co_freevars: tuple, + co_cellvars: tuple, + co_exceptiontable = tuple(), + collection_order: Dict[Union[set, frozenset, dict], Tuple[Any]] = {}, + version_triple: Tuple[int, int, int] = (0, 0, 0), + ) -> None: + self.co_argcount = co_argcount + self.co_posonlyargcount = co_posonlyargcount + self.co_kwonlyargcount = co_kwonlyargcount + self.co_nlocals = co_nlocals + self.co_stacksize = co_stacksize + self.co_flags = co_flags + self.co_code = co_code + self.co_consts = co_consts + self.co_names = co_names + self.co_varnames = co_varnames + self.co_filename = co_filename + self.co_name = co_name + self.co_firstlineno = co_firstlineno # None if 0 in serialized form + self.co_linetable = co_linetable + self.co_qualname = co_qualname + self.co_cellvars = co_cellvars + self.co_linetable = co_linetable + self.co_freevars= co_freevars + self.co_exceptiontable = co_exceptiontable + self.co_collection_order = collection_order + version_triple = version_triple + + def co_lines(self): + return [(sl.line, sl.character_offset, sl.character_offset) for sl in self.co_linetable] diff --git a/xdis/cross_dis.py b/xdis/cross_dis.py index 57fa8e75..80c41357 100644 --- a/xdis/cross_dis.py +++ b/xdis/cross_dis.py @@ -44,9 +44,13 @@ def _try_compile(source: str, name: str) -> CodeType: return c -def code_info(x, version_tuple: Tuple[int, ...], python_implementation: PythonImplementation) -> 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, python_implementation=python_implementation) + return format_code_info( + get_code_object(x), version_tuple, python_implementation=python_implementation + ) def get_code_object(x): @@ -259,7 +263,12 @@ def op_has_argument(opcode: int, opc) -> bool: """ Return True if `opcode` instruction has an operand. """ - return opcode >= opc.HAVE_ARGUMENT + return ( + opcode in opc.hasarg + if hasattr(opc, "hasarg") + and opc.python_implementation is PythonImplementation.RustPython + else opcode >= opc.HAVE_ARGUMENT + ) def pretty_flags(flags, python_implementation=PYTHON_IMPLEMENTATION) -> str: @@ -269,7 +278,10 @@ def pretty_flags(flags, python_implementation=PYTHON_IMPLEMENTATION) -> str: for i in range(32): flag = 1 << i if flags & flag: - if python_implementation == PythonImplementation.PyPy and flag in PYPY_COMPILER_FLAG_NAMES: + if ( + python_implementation == PythonImplementation.PyPy + and flag in PYPY_COMPILER_FLAG_NAMES + ): names.append(PYPY_COMPILER_FLAG_NAMES.get(flag, hex(flag))) else: names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag))) @@ -320,7 +332,9 @@ def format_code_info( pass if version_tuple >= (1, 3): - lines.append("# Flags: %s" % pretty_flags(co.co_flags, python_implementation)) + lines.append( + "# Flags: %s" % pretty_flags(co.co_flags, python_implementation) + ) if version_tuple >= (1, 5): lines.append("# First Line: %s" % co.co_firstlineno) @@ -373,7 +387,9 @@ def format_exception_table(bytecode, version_tuple) -> str: for entry in bytecode.exception_entries: lasti = " lasti" if entry.lasti else "" end = entry.end - 2 - lines.append(f" {entry.start} to {end} -> {entry.target} [{entry.depth}]{lasti}") + lines.append( + f" {entry.start} to {end} -> {entry.target} [{entry.depth}]{lasti}" + ) return "\n".join(lines) @@ -414,7 +430,11 @@ def unpack_opargs_bytecode(code, opc): offset += 1 if op_has_argument(op, opc): arg = code2num(code, offset) | extended_arg - extended_arg = extended_arg_val(opc, arg) if hasattr(opc, "EXTENDED_ARG") and op == opc.EXTENDED_ARG else 0 + extended_arg = ( + extended_arg_val(opc, arg) + if hasattr(opc, "EXTENDED_ARG") and op == opc.EXTENDED_ARG + else 0 + ) offset += 2 else: arg = None @@ -474,7 +494,7 @@ def xstack_effect(opcode, opc, oparg: int = 0, jump=None): if opname == "BUILD_MAP" and version_tuple >= (3, 5): return 1 - (2 * oparg) if opname in ("UNPACK_SEQUENCE",): - return oparg - 1 + return oparg - 1 elif opname in ("UNPACK_EX"): return (oparg & 0xFF) + (oparg >> 8) elif opname == "BUILD_INTERPOLATION": diff --git a/xdis/disasm.py b/xdis/disasm.py index 4274df80..1bc7ba62 100644 --- a/xdis/disasm.py +++ b/xdis/disasm.py @@ -68,6 +68,8 @@ def get_opcode( lookup = "3.12.7Graal" else: lookup += "Graal" + elif python_implementation == PythonImplementation.RustPython: + lookup += "Rust" if lookup in op_imports.keys(): if alternate_opmap is not None: # TODO: change bytecode version number comment line to indicate altered diff --git a/xdis/load.py b/xdis/load.py index 30d03eaa..168293fd 100644 --- a/xdis/load.py +++ b/xdis/load.py @@ -224,7 +224,6 @@ def load_module_from_file_object( timestamp = 0 file_offsets = {} - python_implementation = PythonImplementation.CPython try: magic = fp.read(4) magic_int = magic2int(magic) @@ -250,7 +249,6 @@ def load_module_from_file_object( RUSTPYTHON_MAGICS ) + list(JYTHON_MAGICS): version = magicint2version.get(magic_int, "") - raise ImportError(f"Magic int {magic_int} ({version}) is not supported.") if magic_int in INTERIM_MAGIC_INTS: raise ImportError( @@ -355,8 +353,15 @@ def load_module_from_file_object( finally: fp.close() + python_implementation = PythonImplementation.RustPython if magic_int in RUSTPYTHON_MAGICS else PythonImplementation.CPython if is_pypy(magic_int, filename): python_implementation = PythonImplementation.PyPy + elif magic_int in RUSTPYTHON_MAGICS: + python_implementation = PythonImplementation.RustPython + elif magic_int in GRAAL3_MAGICS: + python_implementation = PythonImplementation.Graal + else: + python_implementation = PythonImplementation.CPython # Below we need to return co.version_triple instead of version_triple, # because Graal uses the *same* magic number but different bytecode diff --git a/xdis/op_imports.py b/xdis/op_imports.py index bc1b0337..b30d2041 100644 --- a/xdis/op_imports.py +++ b/xdis/op_imports.py @@ -64,6 +64,7 @@ opcode_312, opcode_312graal, opcode_313, + opcode_313rust, opcode_314, ) from xdis.version_info import PythonImplementation, version_tuple_to_str @@ -187,6 +188,7 @@ "3.12.0rc2": opcode_312, "3.12.0": opcode_312, "3.13.0rc3": opcode_313, + "3.13.0Rust": opcode_313rust, "3.14b3": opcode_314, "3.14.0": opcode_314, "3.14": opcode_314, diff --git a/xdis/opcodes/base.py b/xdis/opcodes/base.py index 787b6a07..b67201eb 100644 --- a/xdis/opcodes/base.py +++ b/xdis/opcodes/base.py @@ -188,11 +188,15 @@ def call_op( Put opcode in the class of instructions that perform calls. """ loc["callop"].add(opcode) + if "hasarg" in loc: + loc["hasarg"].append(opcode) nargs_op(loc, name, opcode, pop, push, fallthrough) def compare_op(loc: dict, name: str, opcode: int, pop: int = 2, push: int = 1) -> None: def_op(loc, name, opcode, pop, push) + if "hasarg" in loc: + loc["hasarg"].append(opcode) loc["hascompare"].append(opcode) loc["binaryop"].add(opcode) @@ -203,6 +207,8 @@ def conditional_op(loc: dict, name: str, opcode: int) -> None: def const_op(loc: dict, name: str, opcode: int, pop: int = 0, push: int = 1) -> None: def_op(loc, name, opcode, pop, push) + if "hasarg" in loc: + loc["hasarg"].append(opcode) loc["hasconst"].append(opcode) loc["nullaryop"].add(opcode) @@ -225,6 +231,8 @@ def def_op( def free_op(loc: dict, name: str, opcode: int, pop: int = 0, push: int = 1) -> None: def_op(loc, name, opcode, pop, push) + if "hasarg" in loc: + loc["hasarg"].append(opcode) loc["hasfree"].append(opcode) @@ -242,6 +250,8 @@ def jabs_op( """ def_op(loc, name, opcode, pop, push, fallthrough=fallthrough) loc["hasjabs"].append(opcode) + if "hasarg" in loc: + loc["hasarg"].append(opcode) if conditional: loc["hascondition"].append(opcode) @@ -252,12 +262,16 @@ def jrel_op(loc, name: str, opcode: int, pop: int=0, push: int=0, conditional=Fa """ def_op(loc, name, opcode, pop, push, fallthrough) loc["hasjrel"].append(opcode) + if "hasarg" in loc: + loc["hasarg"].append(opcode) if conditional: loc["hascondition"].append(opcode) def local_op(loc, name, opcode: int, pop=0, push=1) -> None: def_op(loc, name, opcode, pop, push) + if "hasarg" in loc: + loc["hasarg"].append(opcode) loc["haslocal"].append(opcode) loc["nullaryop"].add(opcode) @@ -268,6 +282,8 @@ def name_op(loc: dict, op_name, opcode: int, pop=-2, push=-2) -> None: """ def_op(loc, op_name, opcode, pop, push) loc["hasname"].append(opcode) + if "hasarg" in loc: + loc["hasarg"].append(opcode) loc["nullaryop"].add(opcode) @@ -278,6 +294,8 @@ def nargs_op( Put opcode in the class of instructions that have a variable number of (or *n*) arguments """ def_op(loc, name, opcode, pop, push, fallthrough=fallthrough) + if "hasarg" in loc: + loc["hasarg"].append(opcode) loc["hasnargs"].append(opcode) diff --git a/xdis/opcodes/opcode_10.py b/xdis/opcodes/opcode_10.py index d3f513b2..ff0abf78 100644 --- a/xdis/opcodes/opcode_10.py +++ b/xdis/opcodes/opcode_10.py @@ -1,4 +1,4 @@ -# (C) Copyright 2019-2023 by Rocky Bernstein +# (C) Copyright 2019-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 @@ -16,8 +16,8 @@ """ CPython 1.0 bytecode opcodes -This is used in bytecode disassembly. This is similar to the -opcodes in Python's dis.py library. +This is like Python 1.0's dis.py with some classification +of stack usage and information for formatting instructions. """ import xdis.opcodes.opcode_11 as opcode_11 @@ -25,7 +25,7 @@ # This is used from outside this module from xdis.cross_dis import findlabels # noqa from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, name_op, @@ -35,6 +35,7 @@ from xdis.opcodes.opcode_11 import opcode_arg_fmt11, opcode_extended_fmt11 version_tuple = (1, 0) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_11, version_tuple) diff --git a/xdis/opcodes/opcode_11.py b/xdis/opcodes/opcode_11.py index d2a12bc4..b68c9bf3 100644 --- a/xdis/opcodes/opcode_11.py +++ b/xdis/opcodes/opcode_11.py @@ -25,7 +25,7 @@ # This is used from outside this module from xdis.cross_dis import findlabels from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, update_pj2, @@ -33,6 +33,7 @@ from xdis.opcodes.opcode_12 import opcode_arg_fmt12, opcode_extended_fmt12 version_tuple = (1, 1) # 1.2 is the same +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_12, version_tuple) diff --git a/xdis/opcodes/opcode_12.py b/xdis/opcodes/opcode_12.py index 021587bc..0029a016 100644 --- a/xdis/opcodes/opcode_12.py +++ b/xdis/opcodes/opcode_12.py @@ -25,7 +25,7 @@ # This is used from outside this module from xdis.cross_dis import findlabels # noqa from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, name_op, @@ -36,6 +36,7 @@ from xdis.opcodes.opcode_13 import opcode_arg_fmt13, opcode_extended_fmt13 version_tuple = (1, 2) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_13, version_tuple) diff --git a/xdis/opcodes/opcode_13.py b/xdis/opcodes/opcode_13.py index 416467ef..7b69bc60 100644 --- a/xdis/opcodes/opcode_13.py +++ b/xdis/opcodes/opcode_13.py @@ -24,8 +24,8 @@ # This is used from outside this module from xdis.cross_dis import findlabels # noqa -from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa - cpython_implementation as python_implementation, +from xdis.opcodes.base import ( + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -35,6 +35,7 @@ from xdis.opcodes.opcode_1x import opcode_extended_fmt_base1x, update_arg_fmt_base1x version_tuple = (1, 3) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_14, version_tuple) diff --git a/xdis/opcodes/opcode_14.py b/xdis/opcodes/opcode_14.py index b8d24453..8879d0c6 100644 --- a/xdis/opcodes/opcode_14.py +++ b/xdis/opcodes/opcode_14.py @@ -25,6 +25,7 @@ # This is used from outside this module from xdis.cross_dis import findlabels # noqa from xdis.opcodes.base import ( # Although these aren't used here, they are exported + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -35,7 +36,7 @@ from xdis.opcodes.opcode_1x import opcode_extended_fmt_base1x, update_arg_fmt_base1x version_tuple = (1, 4) -python_implementation = "CPython" +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_15, version_tuple) diff --git a/xdis/opcodes/opcode_15.py b/xdis/opcodes/opcode_15.py index c0e666be..2825ea8b 100644 --- a/xdis/opcodes/opcode_15.py +++ b/xdis/opcodes/opcode_15.py @@ -16,14 +16,14 @@ """ CPython 1.5 bytecode opcodes -This is a like Python 1.5's opcode.py with some classification +This is a like Python 1.5's dis.py with some classification of stack usage and information for formatting instructions. of stack usage. """ import xdis.opcodes.opcode_1x as opcode_1x from xdis.opcodes.base import ( # Although these aren't used here, they are exported; noqa - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, update_pj2, @@ -31,6 +31,7 @@ from xdis.opcodes.opcode_1x import opcode_extended_fmt_base1x, update_arg_fmt_base1x version_tuple = (1, 5) +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_16.py b/xdis/opcodes/opcode_16.py index 3cc46b70..97a13868 100644 --- a/xdis/opcodes/opcode_16.py +++ b/xdis/opcodes/opcode_16.py @@ -16,8 +16,9 @@ """ CPython 1.6 bytecode opcodes -This is used in bytecode disassembly. This is similar to the -opcodes in Python's dis.py library. +This is a like Python 1.6's dis.py with some classification +of stack usage and information for formatting instructions. +of stack usage. """ import xdis.opcodes.opcode_15 as opcode_15 @@ -26,7 +27,7 @@ from xdis.cross_dis import findlabels, findlinestarts # noqa from xdis.opcodes.base import ( # noqa call_op, - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, update_pj2, @@ -34,6 +35,7 @@ from xdis.opcodes.opcode_2x import opcode_extended_fmt_base2x, update_arg_fmt_base2x version_tuple = (1, 6) +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_20.py b/xdis/opcodes/opcode_20.py index 0941f960..5dab63d5 100644 --- a/xdis/opcodes/opcode_20.py +++ b/xdis/opcodes/opcode_20.py @@ -21,7 +21,7 @@ import xdis.opcodes.opcode_21 as opcode_21 from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, rm_op, @@ -30,6 +30,7 @@ from xdis.opcodes.opcode_2x import opcode_extended_fmt_base2x, update_arg_fmt_base2x version_tuple = (2, 0) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_21, version_tuple) diff --git a/xdis/opcodes/opcode_21.py b/xdis/opcodes/opcode_21.py index e3340587..210aa591 100644 --- a/xdis/opcodes/opcode_21.py +++ b/xdis/opcodes/opcode_21.py @@ -21,7 +21,7 @@ import xdis.opcodes.opcode_22 as opcode_22 from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, rm_op, @@ -30,6 +30,7 @@ from xdis.opcodes.opcode_2x import opcode_extended_fmt_base2x, update_arg_fmt_base2x version_tuple = (2, 1) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_22, version_tuple) diff --git a/xdis/opcodes/opcode_22.py b/xdis/opcodes/opcode_22.py index b8effafa..520bf865 100644 --- a/xdis/opcodes/opcode_22.py +++ b/xdis/opcodes/opcode_22.py @@ -1,4 +1,18 @@ # (C) Copyright 2017, 2019, 2021, 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. """ CPython 2.2 bytecode opcodes @@ -7,7 +21,7 @@ import xdis.opcodes.opcode_2x as opcode_2x from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -16,6 +30,7 @@ from xdis.opcodes.opcode_2x import opcode_extended_fmt_base2x, update_arg_fmt_base2x version_tuple = (2, 2) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_2x, version_tuple) diff --git a/xdis/opcodes/opcode_23.py b/xdis/opcodes/opcode_23.py index 2035dd43..df871638 100644 --- a/xdis/opcodes/opcode_23.py +++ b/xdis/opcodes/opcode_23.py @@ -1,4 +1,18 @@ # (C) Copyright 2017, 2019-2021, 2023 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. """ CPython 2.3 bytecode opcodes @@ -8,7 +22,7 @@ import xdis.opcodes.opcode_2x as opcode_2x from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, update_pj2, @@ -16,6 +30,7 @@ from xdis.opcodes.opcode_2x import opcode_extended_fmt_base2x, update_arg_fmt_base2x version_tuple = (2, 3) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_2x, version_tuple) diff --git a/xdis/opcodes/opcode_24.py b/xdis/opcodes/opcode_24.py index 5f8eb6ac..b0e93e40 100644 --- a/xdis/opcodes/opcode_24.py +++ b/xdis/opcodes/opcode_24.py @@ -1,4 +1,18 @@ # (C) Copyright 2017, 2020-2021, 2023 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. """ CPython 2.4 bytecode opcodes @@ -8,7 +22,7 @@ import xdis.opcodes.opcode_2x as opcode_2x from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -17,6 +31,7 @@ from xdis.opcodes.opcode_2x import opcode_extended_fmt_base2x, update_arg_fmt_base2x version_tuple = (2, 4) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_2x, version_tuple) diff --git a/xdis/opcodes/opcode_25.py b/xdis/opcodes/opcode_25.py index 3ef75652..c710e7ed 100644 --- a/xdis/opcodes/opcode_25.py +++ b/xdis/opcodes/opcode_25.py @@ -1,4 +1,18 @@ # (C) Copyright 2017, 2020-2021, 2023 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. """ CPython 2.5 bytecode opcodes @@ -8,7 +22,7 @@ import xdis.opcodes.opcode_24 as opcode_24 from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -17,6 +31,7 @@ from xdis.opcodes.opcode_2x import opcode_extended_fmt_base2x, update_arg_fmt_base2x version_tuple = (2, 5) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_24, version_tuple) diff --git a/xdis/opcodes/opcode_26.py b/xdis/opcodes/opcode_26.py index d0839261..57ec226a 100644 --- a/xdis/opcodes/opcode_26.py +++ b/xdis/opcodes/opcode_26.py @@ -22,7 +22,7 @@ import xdis.opcodes.opcode_25 as opcode_25 from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, name_op, @@ -33,6 +33,7 @@ from xdis.opcodes.opcode_2x import opcode_extended_fmt_base2x, update_arg_fmt_base2x version_tuple = (2, 6) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_25, version_tuple) diff --git a/xdis/opcodes/opcode_27.py b/xdis/opcodes/opcode_27.py index 2a074a69..f30762f2 100644 --- a/xdis/opcodes/opcode_27.py +++ b/xdis/opcodes/opcode_27.py @@ -23,7 +23,7 @@ import xdis.opcodes.opcode_26 as opcode_26 from xdis.opcodes.base import ( # noqa compare_op, - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -37,6 +37,7 @@ from xdis.opcodes.opcode_2x import opcode_extended_fmt_base2x, update_arg_fmt_base2x version_tuple = (2, 7) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_26, version_tuple) diff --git a/xdis/opcodes/opcode_30.py b/xdis/opcodes/opcode_30.py index 7597c2d6..f53ac197 100644 --- a/xdis/opcodes/opcode_30.py +++ b/xdis/opcodes/opcode_30.py @@ -1,4 +1,18 @@ # (C) Copyright 2017, 2019-2021, 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. """ CPython 3.0 bytecode opcodes @@ -8,7 +22,7 @@ import xdis.opcodes.opcode_31 as opcode_31 from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -19,6 +33,7 @@ from xdis.opcodes.opcode_33 import opcode_arg_fmt33, opcode_extended_fmt33 version_tuple = (3, 0) +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_31.py b/xdis/opcodes/opcode_31.py index f85074e5..17e41d60 100644 --- a/xdis/opcodes/opcode_31.py +++ b/xdis/opcodes/opcode_31.py @@ -8,7 +8,7 @@ import xdis.opcodes.opcode_32 as opcode_32 from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -21,6 +21,7 @@ loc = locals() version_tuple = (3, 1) +python_implementation = cpython_implementation init_opdata(loc, opcode_32, version_tuple) diff --git a/xdis/opcodes/opcode_310.py b/xdis/opcodes/opcode_310.py index 62fcbd79..409aebf4 100644 --- a/xdis/opcodes/opcode_310.py +++ b/xdis/opcodes/opcode_310.py @@ -23,7 +23,7 @@ import xdis.opcodes.opcode_39 as opcode_39 from xdis.cross_dis import findlinestarts # noqa from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -33,6 +33,7 @@ from xdis.opcodes.opcode_39 import opcode_arg_fmt39, opcode_extended_fmt39 version_tuple = (3, 10) +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_311.py b/xdis/opcodes/opcode_311.py index aa953bf0..fad7ce4a 100644 --- a/xdis/opcodes/opcode_311.py +++ b/xdis/opcodes/opcode_311.py @@ -27,6 +27,7 @@ from xdis.instruction import Instruction from xdis.opcodes.base import ( binary_op, + cpython_implementation, def_op, finalize_opcodes, free_op, @@ -45,7 +46,7 @@ from xdis.opcodes.opcode_310 import opcode_arg_fmt310, opcode_extended_fmt310 version_tuple = (3, 11) -python_implementation = "CPython" +python_implementation = cpython_implementation # oppush[op] => number of stack entries pushed oppush: List[int] = [0] * 256 diff --git a/xdis/opcodes/opcode_312.py b/xdis/opcodes/opcode_312.py index 0a0e0070..49bc7835 100644 --- a/xdis/opcodes/opcode_312.py +++ b/xdis/opcodes/opcode_312.py @@ -1,13 +1,30 @@ +# (C) Copyright 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. """ CPython 3.12 bytecode opcodes -This is like Python 3.12's opcode.py with some classification +This is like Python 3.12's opcode.py with some classification of stack usage and information for formatting instructions. +This has similar imformation as the opcodes in Python's opcode.py library. """ import xdis.opcodes.opcode_311 as opcode_311 from xdis.opcodes.base import ( binary_op, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -20,7 +37,7 @@ from xdis.opcodes.opcode_311 import opcode_arg_fmt311, opcode_extended_fmt311 version_tuple = (3, 12) -python_implementation = "CPython" +python_implementation = cpython_implementation loc = locals() @@ -113,9 +130,9 @@ # OP NAME OPCODE POP PUSH def_op(loc , "SETUP_FINALLY" , 256, 0, 1) -def_op(loc , "SETUP_CLEANUP" , 257, 0, 1) +def_op(loc , "SETUP_CLEANUP" , 257, 0, 2) def_op(loc , "SETUP_WITH" , 258, 0, 1) -def_op(loc , "POP_BLOCK" , 259, 0, 1) +def_op(loc , "POP_BLOCK" , 259, 0, 0) jrel_op(loc , "JUMP" , 260, 0, 0) jrel_op(loc , "JUMP_NO_INTERRUPT" , 261, 0, 0) diff --git a/xdis/opcodes/opcode_312rust.py b/xdis/opcodes/opcode_312rust.py index 3e06b0b9..b657b0c6 100644 --- a/xdis/opcodes/opcode_312rust.py +++ b/xdis/opcodes/opcode_312rust.py @@ -1,6 +1,20 @@ +# (C) Copyright 2019-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. """ -RustPython 3.12 bytecode opcodes - +RustPython 3.12 bytecode opcodes. """ #FIXME: this needs a lot of going over. @@ -108,6 +122,7 @@ def pseudo_op(name: str, op: int, real_ops: list): oplist.append(op) +# See RustPython/compiler/core/src/bytecode.rs Instruction # fmt: off # OP NAME OPCODE POP PUSH diff --git a/xdis/opcodes/opcode_313.py b/xdis/opcodes/opcode_313.py index c54caaba..e160a82d 100644 --- a/xdis/opcodes/opcode_313.py +++ b/xdis/opcodes/opcode_313.py @@ -1,5 +1,24 @@ +# (C) Copyright 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. """ -CPython 3.13 bytecode opcodes +CPython 3.13 bytecode opcodes. + +This is like Python 3.13's opcode.py with some classification +of stack usage and information for formatting instructions. +This has similar imformation as the opcodes in Python's opcode.py library. """ from typing import Optional, Tuple @@ -7,7 +26,7 @@ import xdis.opcodes.opcode_312 as opcode_312 from xdis.opcodes.base import ( # noqa VARYING_STACK_INT, - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, free_op, @@ -20,6 +39,7 @@ from xdis.opcodes.opcode_36pypy import format_CALL_METHOD version_tuple = (3, 13) +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_313rust.py b/xdis/opcodes/opcode_313rust.py new file mode 100644 index 00000000..85422adc --- /dev/null +++ b/xdis/opcodes/opcode_313rust.py @@ -0,0 +1,536 @@ +# (C) Copyright 2019-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. +""" +RustPython 3.13 bytecode opcodes + +""" + +#FIXME: this needs a lot of going over. + +from typing import Dict, List, Optional, Tuple + +# import xdis.opcodes.opcode_313 as opcode_313 +from xdis.opcodes.base import ( + VARYING_STACK_INT, + binary_op, + call_op, + compare_op, + const_op, + def_op, + finalize_opcodes, + free_op, + init_opdata, + jrel_op, + local_op, + name_op, + nargs_op, + store_op, + unary_op, + update_pj3, + varargs_op, +) +from xdis.opcodes.format.extended import extended_format_binary_op +from xdis.opcodes.opcode_313 import opcode_arg_fmt313, opcode_extended_fmt313 +from xdis.version_info import PythonImplementation + +version_tuple = (3, 13) +python_implementation = PythonImplementation("RustPython") + +# oppush[op] => number of stack entries pushed +oppush: List[int] = [0] * 256 + +# oppop[op] => number of stack entries popped +oppop: List[int] = [0] * 256 + +# opmap[opcode_name] => opcode_number +opmap: Dict[str, int] = {} + +## pseudo opcodes (used in the compiler) mapped to the values +## they can become in the actual code. +_pseudo_ops = {} + +_nb_ops = [ + ("NB_ADD", "+"), + ("NB_AND", "&"), + ("NB_FLOOR_DIVIDE", "//"), + ("NB_LSHIFT", "<<"), + ("NB_MATRIX_MULTIPLY", "@"), + ("NB_MULTIPLY", "*"), + ("NB_REMAINDER", "%"), + ("NB_OR", "|"), + ("NB_POWER", "**"), + ("NB_RSHIFT", ">>"), + ("NB_SUBTRACT", "-"), + ("NB_TRUE_DIVIDE", "/"), + ("NB_XOR", "^"), + ("NB_INPLACE_ADD", "+="), + ("NB_INPLACE_AND", "&="), + ("NB_INPLACE_FLOOR_DIVIDE", "//="), + ("NB_INPLACE_LSHIFT", "<<="), + ("NB_INPLACE_MATRIX_MULTIPLY", "@="), + ("NB_INPLACE_MULTIPLY", "*="), + ("NB_INPLACE_REMAINDER", "%="), + ("NB_INPLACE_OR", "|="), + ("NB_INPLACE_POWER", "**="), + ("NB_INPLACE_RSHIFT", ">>="), + ("NB_INPLACE_SUBTRACT", "-="), + ("NB_INPLACE_TRUE_DIVIDE", "/="), + ("NB_INPLACE_XOR", "^="), +] + +hasexc = [] + +loc = locals() + +init_opdata(loc, from_mod=None, version_tuple=version_tuple) + +loc["opname"].extend([f"<{i}>" for i in range(256, 267)]) +loc["oppop"].extend([0] * 11) +loc["oppush"].extend([0] * 11) + +hasarg = [] +hasconst = [] +hasname = [] +hasjrel = [] +hasjabs = [] +haslocal = [] +hascompare = [] +hasfree = [] +hasexc = [] + +oplists = [ + loc["hasarg"], + loc["hasconst"], + loc["hasname"], + loc["hasjrel"], + loc["hasjabs"], + loc["haslocal"], + loc["hascompare"], + loc["hasfree"], + loc["hasexc"], +] + +# add new table "hasjump" +loc.update({"hasjump": []}) +loc["hasjrel"] = loc["hasjump"] + +def pseudo_op(name: str, op: int, real_ops: list): + def_op(loc, name, op) + _pseudo_ops[name] = real_ops + # add the pseudo opcode to the lists its targets are in + for oplist in oplists: + res = [opmap[rop] in oplist for rop in real_ops] + if any(res): + # FIXME: for some reason JUMP_FORWARD appears to be + # listed as a free op. It isn't. + # if not all(res): + # breakpoint() + # assert all(res) + oplist.append(op) + + +# See RustPython/compiler/core/src/bytecode.rs Instruction +# fmt: off + +# OP NAME OPCODE POP PUSH +#---------------------------------------------------------- +def_op(loc, "NOP", 0, 0, 0) +name_op(loc, "IMPORT_NAME", 1) +def_op(loc, "IMPORT_NAMELESS", 2, 0, 1) +name_op(loc, "IMPORT_FROM", 3) + +local_op(loc, "LOAD_FAST", 4) +name_op(loc, "LOAD_NAME_ANY", 5) +name_op(loc, "LOAD_GLOBAL", 6) +free_op(loc, "LOAD_DEREF", 7) +def_op(loc, "LOAD_CLASS_DEREF", 8) + +store_op(loc, " STORE_FAST", 9, 1, 0, is_type="local") # Local variable +store_op(loc, "STORE_LOCAL", 10, 4, 0) +name_op(loc, "STORE_GLOBAL", 11) # "" +store_op(loc, "STORE_DEREF", 12, 1, 0, is_type="free") + +local_op(loc, "DELETE_FAST", 13, 0, 0) # Local variable number is in operand +local_op(loc, "DELETE_LOCAL", 14, 0, 0) +name_op(loc, "DELETE_GLOBAL", 15) # "" +free_op(loc, "DELETE_DEREF", 16, 0, 0) + +free_op(loc, "LOAD_CLOSURE", 17) +def_op(loc, "SUBSCRIPT", 18, 0, 0) + +def_op(loc, "STORE_SUBSCR", 19, 0, 0) +def_op(loc, "DELETE_SUBSCR", 20, 0, 0) +name_op(loc, "STORE_ATTR", 21) # Index in name list +name_op(loc, "DELETE_ATTR", 22) # "" + +# Operand is in const list +const_op(loc, "LOAD_CONST", 23, 0, 1) +unary_op(loc, "UNARY_OP", 24, 1, 1) +binary_op(loc, "BINARY_OP", 25, 1, 1) +binary_op(loc, "BINARY_OP_INPLACE", 26, 1, 1) +binary_op(loc, "BINARY_SUBCR", 27, 1, 1) +name_op(loc, "LOAD_ATTR", 28) # Index in name list +compare_op(loc, "TEST_OP", 127, 2, 1) # test operator +compare_op(loc, "COMPARE_OP", 138, 2, 1) # Comparison operator +def_op(loc, "COPY", 29) +def_op(loc, "POP_TOP", 30, 1, 0) +def_op(loc, "SWAP", 31, 1, 1) +def_op(loc, "TO_BOOL", 32, 1, 1) +def_op(loc, "ROT_TWO", 33, 2, 2) +def_op(loc, "ROT_THREE", 34, 3, 3) + +nargs_op(loc, "MAKE_FUNCTION", 42) # Flags +def_op(loc, "SET_FUNCTION_ATTR", 43) +call_op(loc, "CALL_FUNCTION", 44) +call_op(loc, "CALL_FUNCTION_KW", 45) +call_op(loc, "CALL_FUNCTION_EX", 46) # Flags +def_op(loc, "LOAD_METHOD", 47) +call_op(loc, "CALL_METHOD", 48) +call_op(loc, "CALL_METHOD_KW", 49) +call_op(loc, "CALL_METHOD_EX", 50) +jrel_op(loc, "FOR_ITER", 51) +def_op(loc, "RETURN_VALUE", 52) +const_op(loc, "RETURN_CONST", 53) +# This must be kept in sync with deepfreeze.py +# resume, acts like a nop +def_op(loc, "RESUME", 54, 0, 0) +def_op(loc, "YIELD", 55) +# TODO: RUSTPYTHON +# Delete below def_op after updating coroutines.py +def_op(loc, "YIELD_FROM", 56) + + +jrel_op(loc, "PUSH_EXC_INFO", 235, 0, 1) +def_op(loc, "CHECK_EXC_MATCH", 236) +jrel_op(loc, "CHECK_EG_MATCH", 237, 0, 0) + +# FIXME: fill in +def_op(loc, "WITH_EXCEPT_START", 249) +def_op(loc, "GET_AITER", 250) +def_op(loc, "GET_ANEXT", 251) +def_op(loc, "BEFORE_ASYNC_WITH", 252) +def_op(loc, "BEFORE_WITH", 253) +def_op(loc, "END_ASYNC_FOR", 254) +def_op(loc, "CLEANUP_THROW", 255) + +def_op(loc, "GET_ITER", 68) +def_op(loc, "GET_YIELD_FROM_ITER", 69) +def_op(loc, "PRINT_EXPR", 70) +def_op(loc, "LOAD_BUILD_CLASS", 71) + +def_op(loc, "LOAD_ASSERTION_ERROR", 74) +def_op(loc, "RETURN_GENERATOR", 75) + +def_op(loc, "LIST_TO_TUPLE", 82) + +def_op(loc, "IMPORT_STAR", 84) +def_op(loc, "SETUP_ANNOTATIONS", 85) + +def_op(loc, "ASYNC_GEN_WRAP", 87) +def_op(loc, "PREP_RERAISE_STAR", 88) +def_op(loc, "POP_EXCEPT", 89) + +HAVE_ARGUMENT = 90 # real opcodes from here have an argument: + +name_op(loc, "STORE_NAME", 90) # Index in name list +name_op(loc, "DELETE_NAME", 91) # "" +varargs_op(loc, "UNPACK_SEQUENCE", 92, 1, VARYING_STACK_INT) # Number of tuple items +def_op(loc, "UNPACK_EX", 94, VARYING_STACK_INT, VARYING_STACK_INT) + +name_op(loc, "LOAD_NAME", 101) # Index in name list +def_op(loc, "BUILD_TUPLE", 102) # Number of tuple items +def_op(loc, "BUILD_LIST", 103) # Number of list items +def_op(loc, "BUILD_SET", 104) # Number of set items +def_op(loc, "BUILD_MAP", 105) # Number of dict entries +name_op(loc, "IMPORT_FROM", 109) # Index in name list +jrel_op(loc, "JUMP_FORWARD", 110) # Number of words to skip +jrel_op(loc, "JUMP_IF_FALSE_OR_POP", 111) # Number of words to skip +jrel_op(loc, "JUMP_IF_TRUE_OR_POP", 112) # "" +jrel_op(loc, "POP_JUMP_IF_FALSE", 114) +jrel_op(loc, "POP_JUMP_IF_TRUE", 115) +def_op(loc, "IS_OP", 117) +def_op(loc, "CONTAINS_OP", 118) +def_op(loc, "RERAISE", 119, 3, 0) +def_op(loc, "BINARY_OP", 122) +jrel_op(loc, "SEND", 123) # Number of bytes to skip +local_op(loc, "LOAD_FAST", 124, 0, 1) # Local variable number +loc["nullaryloadop"].add(124) +def_op(loc , "LOAD_FAST_CHECK" , 127, 0, 1) +local_op(loc, "LOAD_FAST_CHECK", 127) # Local variable number +jrel_op(loc, "POP_JUMP_IF_NOT_NONE", 128) +jrel_op(loc, "POP_JUMP_IF_NONE", 129) +def_op(loc, "RAISE_VARARGS", 130) # Number of raise arguments (1, 2, or 3) +def_op(loc, "GET_AWAITABLE", 131) +def_op(loc, "BUILD_SLICE", 133) # Number of items +jrel_op(loc, "JUMP_BACKWARD_NO_INTERRUPT", 134) # Number of words to skip (backwards) +free_op(loc, "MAKE_CELL", 135, 0, 0) +free_op(loc, "LOAD_DEREF", 137, 0, 1) +loc["nullaryop"].add(137) +loc["nullaryloadop"].add(137) +jrel_op(loc, "JUMP_BACKWARD", 140) # Number of words to skip (backwards) + +def_op(loc, "EXTENDED_ARG", 144) +EXTENDED_ARG = 144 +def_op(loc, "LIST_APPEND", 145) +def_op(loc, "SET_ADD", 146) +def_op(loc, "MAP_ADD", 147) +free_op(loc, "LOAD_CLASSDEREF", 148) +def_op(loc, "COPY_FREE_VARS", 149) +def_op(loc, "YIELD_VALUE", 150) + + +def_op(loc, "MATCH_CLASS", 152) + +def_op(loc, "FORMAT_VALUE", 155) +def_op(loc, "BUILD_CONST_KEY_MAP", 156) +def_op(loc, "BUILD_STRING", 157) + +def_op(loc, "LIST_EXTEND", 162) +def_op(loc, "SET_UPDATE", 163) +def_op(loc, "DICT_MERGE", 164) +def_op(loc, "DICT_UPDATE", 165) + +const_op(loc, "KW_NAMES", 172) + +MIN_PSEUDO_OPCODE = 256 + +pseudo_op("SETUP_FINALLY", 256, ["NOP"]) +hasexc.append(256) +pseudo_op("SETUP_CLEANUP", 257, ["NOP"]) +hasexc.append(257) +pseudo_op("SETUP_WITH", 258, ["NOP"]) +hasexc.append(258) +pseudo_op("POP_BLOCK", 259, ["NOP"]) + +pseudo_op("JUMP", 260, ["JUMP_FORWARD", "JUMP_BACKWARD"]) +pseudo_op("JUMP_NO_INTERRUPT", 261, ["JUMP_FORWARD", "JUMP_BACKWARD_NO_INTERRUPT"]) + +def_op(loc, "MATCH_SEQUENCE", 32, 0, 1) +def_op(loc, "MATCH_KEYS", 33, 0, 2) + + + +# fmt: on + +MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1 + +# extend opcodes to cover pseudo ops + +opname = [f"<{op!r}>" for op in range(MAX_PSEUDO_OPCODE + 1)] +opname.extend([f"<{i}>" for i in range(256, 267)]) +oppop.extend([0] * 11) +oppush.extend([0] * 11) + +for op, i in opmap.items(): + opname[i] = op + + +_specializations = { + "BINARY_OP": [ + "BINARY_OP_ADAPTIVE", + "BINARY_OP_ADD_FLOAT", + "BINARY_OP_ADD_INT", + "BINARY_OP_ADD_UNICODE", + "BINARY_OP_INPLACE_ADD_UNICODE", + "BINARY_OP_MULTIPLY_FLOAT", + "BINARY_OP_MULTIPLY_INT", + "BINARY_OP_SUBTRACT_FLOAT", + "BINARY_OP_SUBTRACT_INT", + ], + "BINARY_SUBSCR": [ + "BINARY_SUBSCR_ADAPTIVE", + "BINARY_SUBSCR_DICT", + "BINARY_SUBSCR_GETITEM", + "BINARY_SUBSCR_LIST_INT", + "BINARY_SUBSCR_TUPLE_INT", + ], + "CALL": [ + "CALL_ADAPTIVE", + "CALL_PY_EXACT_ARGS", + "CALL_PY_WITH_DEFAULTS", + "CALL_BOUND_METHOD_EXACT_ARGS", + "CALL_BUILTIN_CLASS", + "CALL_BUILTIN_FAST_WITH_KEYWORDS", + "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", + "CALL_NO_KW_BUILTIN_FAST", + "CALL_NO_KW_BUILTIN_O", + "CALL_NO_KW_ISINSTANCE", + "CALL_NO_KW_LEN", + "CALL_NO_KW_LIST_APPEND", + "CALL_NO_KW_METHOD_DESCRIPTOR_FAST", + "CALL_NO_KW_METHOD_DESCRIPTOR_NOARGS", + "CALL_NO_KW_METHOD_DESCRIPTOR_O", + "CALL_NO_KW_STR_1", + "CALL_NO_KW_TUPLE_1", + "CALL_NO_KW_TYPE_1", + ], + "COMPARE_OP": [ + "COMPARE_OP_ADAPTIVE", + "COMPARE_OP_FLOAT_JUMP", + "COMPARE_OP_INT_JUMP", + "COMPARE_OP_STR_JUMP", + ], + "EXTENDED_ARG": [ + "EXTENDED_ARG_QUICK", + ], + "FOR_ITER": [ + "FOR_ITER_ADAPTIVE", + "FOR_ITER_LIST", + "FOR_ITER_RANGE", + ], + "JUMP_BACKWARD": [ + "JUMP_BACKWARD_QUICK", + ], + "LOAD_ATTR": [ + "LOAD_ATTR_ADAPTIVE", + # These potentially push [NULL, bound method] onto the stack. + "LOAD_ATTR_CLASS", + "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", + "LOAD_ATTR_INSTANCE_VALUE", + "LOAD_ATTR_MODULE", + "LOAD_ATTR_PROPERTY", + "LOAD_ATTR_SLOT", + "LOAD_ATTR_WITH_HINT", + # These will always push [unbound method, self] onto the stack. + "LOAD_ATTR_METHOD_LAZY_DICT", + "LOAD_ATTR_METHOD_NO_DICT", + "LOAD_ATTR_METHOD_WITH_DICT", + "LOAD_ATTR_METHOD_WITH_VALUES", + ], + "LOAD_CONST": [ + "LOAD_CONST__LOAD_FAST", + ], + "LOAD_FAST": [ + "LOAD_FAST__LOAD_CONST", + "LOAD_FAST__LOAD_FAST", + ], + "LOAD_GLOBAL": [ + "LOAD_GLOBAL_ADAPTIVE", + "LOAD_GLOBAL_BUILTIN", + "LOAD_GLOBAL_MODULE", + ], + "RESUME": [ + "RESUME_QUICK", + ], + "STORE_ATTR": [ + "STORE_ATTR_ADAPTIVE", + "STORE_ATTR_INSTANCE_VALUE", + "STORE_ATTR_SLOT", + "STORE_ATTR_WITH_HINT", + ], + "STORE_FAST": [ + "STORE_FAST__LOAD_FAST", + "STORE_FAST__STORE_FAST", + ], + "STORE_SUBSCR": [ + "STORE_SUBSCR_ADAPTIVE", + "STORE_SUBSCR_DICT", + "STORE_SUBSCR_LIST_INT", + ], + "UNPACK_SEQUENCE": [ + "UNPACK_SEQUENCE_ADAPTIVE", + "UNPACK_SEQUENCE_LIST", + "UNPACK_SEQUENCE_TUPLE", + "UNPACK_SEQUENCE_TWO_TUPLE", + ], +} +_specialized_instructions = [ + opcode for family in _specializations.values() for opcode in family +] +_specialization_stats = [ + "success", + "failure", + "hit", + "deferred", + "miss", + "deopt", +] + +_cache_format = { + "LOAD_GLOBAL": { + "counter": 1, + "index": 1, + "module_keys_version": 2, + "builtin_keys_version": 1, + }, + "BINARY_OP": { + "counter": 1, + }, + "UNPACK_SEQUENCE": { + "counter": 1, + }, + "COMPARE_OP": { + "counter": 1, + "mask": 1, + }, + "BINARY_SUBSCR": { + "counter": 1, + "type_version": 2, + "func_version": 1, + }, + "FOR_ITER": { + "counter": 1, + }, + "LOAD_ATTR": { + "counter": 1, + "version": 2, + "keys_version": 2, + "descr": 4, + }, + "STORE_ATTR": { + "counter": 1, + "version": 2, + "index": 1, + }, + "CALL": { + "counter": 1, + "func_version": 2, + "min_args": 1, + }, + "STORE_SUBSCR": { + "counter": 1, + }, +} + +_inline_cache_entries = [ + sum(_cache_format.get(opname[opcode], {}).values()) for opcode in range(256) +] + + +def extended_format_BINARY_OP(opc, instructions) -> Tuple[str, Optional[int]]: + opname = _nb_ops[instructions[0].argval][1] + if opname == "%": + opname = "%%" + elif opname == "%=": + opname = "%%=" + return extended_format_binary_op(opc, instructions, f"%s {opname} %s") + + +opcode_extended_fmt313rust = {} +opcode_arg_fmt = opcode_arg_fmt13rust = {} + +### update arg formatting +opcode_extended_fmt = opcode_extended_fmt312rust = { + **opcode_extended_fmt313, + **{ + "BINARY_OP": extended_format_BINARY_OP, + }, +} + +from xdis.opcodes.opcode_311 import findlinestarts + +update_pj3(globals(), loc) +finalize_opcodes(loc) diff --git a/xdis/opcodes/opcode_314.py b/xdis/opcodes/opcode_314.py index 02a6e761..5a2466d9 100644 --- a/xdis/opcodes/opcode_314.py +++ b/xdis/opcodes/opcode_314.py @@ -1,5 +1,24 @@ +# (C) Copyright 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. """ CPython 3.14 bytecode opcodes + +This is a like Python 3.14's opcode.py with some classification +of stack usage and information for formatting instructions. +of stack usage. """ from xdis.opcodes.base import ( # noqa @@ -8,7 +27,7 @@ call_op, compare_op, const_op, - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, free_op, @@ -23,6 +42,7 @@ ) version_tuple = (3, 14) +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_32.py b/xdis/opcodes/opcode_32.py index 32715e03..0724f752 100644 --- a/xdis/opcodes/opcode_32.py +++ b/xdis/opcodes/opcode_32.py @@ -8,7 +8,7 @@ import xdis.opcodes.opcode_3x as opcode_3x from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, finalize_opcodes, init_opdata, update_pj3, @@ -18,6 +18,7 @@ # FIXME: can we DRY this even more? version_tuple = (3, 2) +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_33.py b/xdis/opcodes/opcode_33.py index 175d6c3c..5cb63a75 100644 --- a/xdis/opcodes/opcode_33.py +++ b/xdis/opcodes/opcode_33.py @@ -1,4 +1,4 @@ -# (C) Copyright 2017, 2020-2021, 2023 by Rocky Bernstein +# (C) Copyright 2017, 2020-2021, 2023, 2025 by Rocky Bernstein """ CPython 3.3 bytecode opcodes @@ -8,7 +8,7 @@ import xdis.opcodes.opcode_3x as opcode_3x from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -20,6 +20,7 @@ from xdis.opcodes.opcode_3x import format_MAKE_FUNCTION_30_35 version_tuple = (3, 3) +python_implementation = cpython_implementation loc = locals() init_opdata(loc, opcode_3x, version_tuple) diff --git a/xdis/opcodes/opcode_34.py b/xdis/opcodes/opcode_34.py index 2d91d0d0..89a38720 100644 --- a/xdis/opcodes/opcode_34.py +++ b/xdis/opcodes/opcode_34.py @@ -21,8 +21,8 @@ """ import xdis.opcodes.opcode_33 as opcode_33 -from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, +from xdis.opcodes.base import ( + cpython_implementation, finalize_opcodes, free_op, init_opdata, @@ -32,6 +32,7 @@ from xdis.opcodes.opcode_33 import opcode_arg_fmt33, opcode_extended_fmt33 version_tuple = (3, 4) +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_35.py b/xdis/opcodes/opcode_35.py index 5136c39e..657f6917 100644 --- a/xdis/opcodes/opcode_35.py +++ b/xdis/opcodes/opcode_35.py @@ -1,4 +1,4 @@ -# (C) Copyright 2016-2017, 2020-2021, 2023 by Rocky Bernstein +# (C) Copyright 2016-2017, 2020-2021, 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 @@ -24,8 +24,8 @@ from typing import Optional, Tuple import xdis.opcodes.opcode_34 as opcode_34 -from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, +from xdis.opcodes.base import ( + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -38,7 +38,7 @@ from xdis.opcodes.opcode_34 import opcode_arg_fmt34, opcode_extended_fmt34 version_tuple = (3, 5) - +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_37.py b/xdis/opcodes/opcode_37.py index 0683c1cc..32cdcde5 100644 --- a/xdis/opcodes/opcode_37.py +++ b/xdis/opcodes/opcode_37.py @@ -26,7 +26,7 @@ import xdis.opcodes.opcode_36 as opcode_36 from xdis.opcodes.base import ( # noqa call_op, - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -46,7 +46,7 @@ ) version_tuple = (3, 7) -python_implementation = "CPython" +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_38.py b/xdis/opcodes/opcode_38.py index d1e1594b..fde08200 100644 --- a/xdis/opcodes/opcode_38.py +++ b/xdis/opcodes/opcode_38.py @@ -21,8 +21,8 @@ """ import xdis.opcodes.opcode_37 as opcode_37 -from xdis.opcodes.base import ( # noqa - cpython_implementation as python_implementation, +from xdis.opcodes.base import ( + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -34,6 +34,7 @@ from xdis.opcodes.opcode_37 import opcode_arg_fmt37, opcode_extended_fmt37 version_tuple = (3, 8) +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/opcodes/opcode_39.py b/xdis/opcodes/opcode_39.py index 983c3696..3040fa74 100644 --- a/xdis/opcodes/opcode_39.py +++ b/xdis/opcodes/opcode_39.py @@ -1,4 +1,4 @@ -# (C) Copyright 2019-2021, 2023-2024 by Rocky Bernstein +# (C) Copyright 2019-2021, 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 @@ -23,9 +23,9 @@ from typing import Optional, Tuple import xdis.opcodes.opcode_38 as opcode_38 -from xdis.opcodes.base import ( # noqa +from xdis.opcodes.base import ( binary_op, - cpython_implementation as python_implementation, + cpython_implementation, def_op, finalize_opcodes, init_opdata, @@ -37,7 +37,7 @@ from xdis.opcodes.opcode_38 import opcode_arg_fmt38, opcode_extended_fmt38 version_tuple = (3, 9) -python_implementation = "CPython" +python_implementation = cpython_implementation loc = locals() diff --git a/xdis/unmarsh_graal.py b/xdis/unmarsh_graal.py index 5b39d98d..2f860969 100644 --- a/xdis/unmarsh_graal.py +++ b/xdis/unmarsh_graal.py @@ -29,7 +29,6 @@ from typing import Any, Dict, Tuple, Union from xdis.codetype import to_portable -from xdis.cross_types import LongTypeForPython3 from xdis.magics import GRAAL3_MAGICS, magic_int2tuple from xdis.unmarshal import ( TYPE_ARRAY, @@ -37,7 +36,6 @@ TYPE_ASCII_INTERNED, TYPE_BINARY_COMPLEX, TYPE_BINARY_FLOAT, - TYPE_CODE, TYPE_COMPLEX, TYPE_DICT, TYPE_ELLIPSIS, @@ -69,14 +67,6 @@ ) from xdis.version_info import IS_GRAAL, PYTHON_VERSION_TRIPLE, version_tuple_to_str - -def long(n: int) -> LongTypeForPython3: - return LongTypeForPython3(n) - - # FIXME: we should write a bytes() class with a repr - # that prints the b'' prefix so that Python2 can - # print out Python3 code correctly - # Graal Array types TYPE_CODE_GRAAL = "C" ARRAY_TYPE_BOOLEAN = "B" @@ -138,22 +128,6 @@ def long(n: int) -> LongTypeForPython3: JAVA_MARSHAL_SHIFT = 15 JAVA_MARSHAL_BASE = 1 << JAVA_MARSHAL_SHIFT -def compat_str(s: Union[str, bytes]) -> Union[str, bytes]: - """ - This handles working with strings between Python2 and Python3. - """ - if isinstance(s, bytes): - return s.decode("utf-8", errors="ignore") - elif not isinstance(s, str): - return str(s) - else: - return s - - -def compat_u2s(u) -> str: - return str(u) - - class VersionIndependentUnmarshallerGraal(VersionIndependentUnmarshaller): def __init__(self, fp, magic_int, bytes_for_s, code_objects={}) -> None: """ diff --git a/xdis/unmarsh_rust.py b/xdis/unmarsh_rust.py new file mode 100644 index 00000000..8e4f28ae --- /dev/null +++ b/xdis/unmarsh_rust.py @@ -0,0 +1,285 @@ +# Copyright (c) 2015-2021, 2024-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. + +"""RustPython version-independent Python object deserialization (unmarshal). + +This is needed when the bytecode extracted is from +a different version than the currently-running Python. + +When the running interpreter and the read-in bytecode are the same, +you can use Python's built-in ``marshal.loads()`` to produce a code +object. +""" + +from struct import unpack +from typing import Any, Dict, List, Tuple, Union + +from xdis.codetype.code313rust import Code313Rust, SourceLocation +from xdis.magics import magic_int2tuple +from xdis.unmarshal import ( + TYPE_ASCII, + TYPE_ASCII_INTERNED, + TYPE_BINARY_COMPLEX, + TYPE_BINARY_FLOAT, + TYPE_CODE, + TYPE_DICT, + TYPE_ELLIPSIS, + TYPE_FALSE, + TYPE_FROZENSET, + TYPE_INT, + TYPE_INT64, + TYPE_INTERNED, + TYPE_LIST, + TYPE_LONG, + TYPE_NONE, + TYPE_NULL, + TYPE_REF, + TYPE_SET, + TYPE_SHORT_ASCII, + TYPE_SHORT_ASCII_INTERNED, + TYPE_SLICE, + TYPE_SMALL_TUPLE, + TYPE_STOPITER, + TYPE_STRING, + TYPE_STRINGREF, + TYPE_TRUE, + TYPE_TUPLE, + TYPE_UNICODE, + TYPE_UNKNOWN, + VersionIndependentUnmarshaller, + compat_str, +) +from xdis.version_info import version_tuple_to_str + +# Bit set on marshalType if we should +# add obj to intern_objects. +# FLAG_REF is the marshal.c name +FLAG_REF = 0x80 + +TYPE_COMPLEX_RUSTPYTHON = "y" +TYPE_FLOAT_RUSTPYTHON = "g" + +# The keys in the following dictionary are unmarshal codes, like "s", +# "c", "<", etc. The values of the dictionary are names of routines +# to call that do the data unmarshaling. +# +# Note: we could eliminate the parameters if this were all inside a +# class. This might be good from an efficiency standpoint, and bad +# from a functional-programming standpoint. Pick your poison. +# EDIT: I'm choosing efficiency over functional programming. +UNMARSHAL_DISPATCH_TABLE = { + TYPE_ASCII: "ASCII", + TYPE_ASCII_INTERNED: "ASCII_interned", + TYPE_BINARY_COMPLEX: "binary_complex", + TYPE_BINARY_FLOAT: "binary_float", + TYPE_COMPLEX_RUSTPYTHON: "complex", + TYPE_CODE: "code_rust", + TYPE_DICT: "dict", + TYPE_ELLIPSIS: "Ellipsis", + TYPE_FALSE: "False", + TYPE_FLOAT_RUSTPYTHON: "float", + TYPE_FROZENSET: "frozenset", + TYPE_INT64: "int64", + TYPE_INT: "int32", + TYPE_INTERNED: "interned", + TYPE_LIST: "list", + TYPE_LONG: "long", + TYPE_NONE: "None", + TYPE_NULL: "C_NULL", + TYPE_REF: "object_reference", + TYPE_SET: "set", + TYPE_SHORT_ASCII: "short_ASCII", + TYPE_SHORT_ASCII_INTERNED: "short_ASCII_interned", + TYPE_SLICE: "slice", + TYPE_SMALL_TUPLE: "small_tuple", + TYPE_STOPITER: "stopIteration", + TYPE_STRING: "string", + TYPE_STRINGREF: "python2_string_reference", + TYPE_TRUE: "True", + TYPE_TUPLE: "tuple", + TYPE_UNICODE: "unicode", + TYPE_UNKNOWN: "unknown", +} + +class MarshalError(Exception): + pass + +class VersionIndependentUnmarshallerRust(VersionIndependentUnmarshaller): + def __init__(self, fp, magic_int, bytes_for_s, code_objects={}) -> None: + """ + Marshal versions: + 4: [3.4a3, 3.13) (self.magic_int: 3280 onwards) + 5: [3.14, current) (self.magic_int: circa 3608) + + In Python 3, a ``bytes`` type is used for strings. + """ + self.fp = fp + self.magic_int = magic_int + self.code_objects = code_objects + + # Save a list of offsets in the bytecode file where code + # objects starts. + self.code_to_file_offsets = {} + + # It is helpful to save the order in sets, frozensets and dictionary keys, + # so that on writing a bytecode file we can duplicate this order. + self.collection_order: Dict[Union[set, frozenset, dict], Tuple[Any]] = {} + + self.bytes_for_s = bytes_for_s + self.version_triple = magic_int2tuple(self.magic_int) + if self.version_triple >= (3, 14): + self.marshal_version = 5 + elif (3, 14) > self.version_triple >= (3, 4): + if self.magic_int in (12897,): + self.marshal_version = 3 + else: + self.marshal_version = 4 + else: + assert False, f"version {version_tuple_to_str(self.version.triple)} is not a graal version" + + self.intern_strings = [] + self.intern_objects = [] + self.is_graal = False + self.is_pypy = False + self.is_rust = True + + self.UNMARSHAL_DISPATCH_TABLE = UNMARSHAL_DISPATCH_TABLE + + def t_code_rust(self, save_ref, bytes_for_s: bool = False) -> Code313Rust: + # read instructions + instr_count = self.read_int32() + co_code = self.read_slice(instr_count * 2) + + instructions = [ + int.from_bytes(co_code[i:i+2], "little") for i in range(0, len(co_code), 2) + ] + + # read locations + loc_count = self.read_int32() + locations: List[SourceLocation] = [] + for _ in range(loc_count): + line = self.read_int32() + if line == 0: + raise MarshalError("invalid source location") + # OneIndexed::new used in Rust requires line != 0 + char_off_zero_indexed = self.read_int32() + locations.append( + SourceLocation( + line=line, + character_offset=char_off_zero_indexed + 1 # convert from zero-indexed + ) + ) + + flags = self.read_int16() + + posonlyarg_count = self.read_int32() + arg_count = self.read_int32() + kwonlyarg_count = self.read_int32() + + # source_path + src_len = self.t_int32(save_ref, bytes_for_s) + source_path = self.read_string(src_len, False) + + first_line_raw = self.read_int32() + first_line_number = None if first_line_raw == 0 else first_line_raw + + max_stackdepth = self.read_int32() + + obj_name_len = self.read_int32() + obj_name = self.read_string(obj_name_len, False) + + qualname_len = self.read_int32() + co_qualname = self.read_string(qualname_len, False) + + cell2arg = None + if self.magic_int not in (12897,): + cell2arg_len = self.read_int32() + if cell2arg_len != 0: + cell2arg = [] + for _ in range(cell2arg_len): + raw = self.read_int32() + # convert raw (u32) to signed i32 + signed = raw if raw < (1 << 31) else raw - (1 << 32) + cell2arg.append(signed) + + # constants + const_count = self.read_int32() + constants = [] + for _ in range(const_count): + # deserialize_value must exist in your runtime; it is the + # counterpart of the Rust deserialize_value() + constants.append(self.r_object(bytes_for_s=bytes_for_s)) + + def read_names(): + n = self.read_int32() + out = [] + for _ in range(n): + length = self.read_int32() + out.append(self.read_string(length, False)) + return out + + co_names = read_names() + co_varnames = read_names() + co_cellvars = read_names() + co_freevars = read_names() + co_nlocals = 0 + + if self.magic_int not in (12897,): + linetable_len = self.read_int32() + co_linetable = self.read_slice(linetable_len) + + exceptiontable_len = self.read_int32() + co_exceptiontable = self.read_slice(exceptiontable_len) + else: + co_linetable = b'' + co_exceptiontable = b'' + + return Code313Rust( + co_argcount=arg_count, + co_posonlyargcount=posonlyarg_count, + co_kwonlyargcount=kwonlyarg_count, + co_nlocals=co_nlocals, + co_stacksize=max_stackdepth, + co_flags=flags, + co_code=co_code, + co_consts=constants, + co_names=co_names, + co_varnames=co_varnames, + co_filename=source_path, + co_name=obj_name, + co_qualname=co_qualname, + co_firstlineno=first_line_number, + co_linetable=locations, + co_freevars=co_freevars, + co_cellvars=co_cellvars, + co_exceptiontable=co_exceptiontable, + ) + + + def read_int16(self): + return int(unpack(" bytes: + return self.fp.read(n) + + def read_string(self, n: int, bytes_for_s: bool=False) -> Union[bytes, str]: + s = self.read_slice(n) + if not bytes_for_s: + s = compat_str(s) + return s diff --git a/xdis/unmarshal.py b/xdis/unmarshal.py index f1002d95..fc347218 100644 --- a/xdis/unmarshal.py +++ b/xdis/unmarshal.py @@ -35,7 +35,6 @@ from xdis.codetype import to_portable from xdis.cross_types import LongTypeForPython3, UnicodeForPython3 from xdis.magics import GRAAL3_MAGICS, PYPY3_MAGICS, RUSTPYTHON_MAGICS, magic_int2tuple -from xdis.version_info import version_tuple_to_str def long(n: int) -> LongTypeForPython3: @@ -89,18 +88,6 @@ def long(n: int) -> LongTypeForPython3: TYPE_UNICODE = "u" TYPE_UNKNOWN = "?" -# Graal Array types -ARRAY_TYPE_BOOLEAN = "B" -ARRAY_TYPE_BYTE = "b" -ARRAY_TYPE_DOUBLE = "d" -ARRAY_TYPE_INT = "i" -ARRAY_TYPE_LONG = "l" -ARRAY_TYPE_OBJECT = "o" -ARRAY_TYPE_SHORT = "s" -ARRAY_TYPE_SLICE = ":" -ARRAY_TYPE_STRING = "S" - - # The keys in the following dictionary are unmarshal codes, like "s", # "c", "<", etc. The values of the dictionary are names of routines # to call that do the data unmarshaling. @@ -206,11 +193,6 @@ def __init__(self, fp, magic_int, bytes_for_s, code_objects={}) -> None: self.is_pypy = magic_int in PYPY3_MAGICS self.is_rust = magic_int in RUSTPYTHON_MAGICS - if magic_int in RUSTPYTHON_MAGICS: - raise NotImplementedError( - f"RustPython {version_tuple_to_str(self.version_triple)} is not supported yet." - ) - self.UNMARSHAL_DISPATCH_TABLE = UNMARSHAL_DISPATCH_TABLE def load(self): @@ -812,6 +794,11 @@ def load_code(fp, magic_int, bytes_for_s: bool = False, code_objects={}): if magic_int in GRAAL3_MAGICS: from xdis.unmarsh_graal import VersionIndependentUnmarshallerGraal um_gen = VersionIndependentUnmarshallerGraal(fp, magic_int, bytes_for_s, code_objects) + elif magic_int in RUSTPYTHON_MAGICS: + from xdis.unmarsh_rust import VersionIndependentUnmarshallerRust + um_gen = VersionIndependentUnmarshallerRust( + fp, magic_int, bytes_for_s, code_objects + ) else: um_gen = VersionIndependentUnmarshaller( fp, magic_int, bytes_for_s, code_objects=code_objects @@ -829,6 +816,11 @@ def load_code_and_get_file_offsets( um_gen = VersionIndependentUnmarshallerGraal( fp, magic_int, bytes_for_s, code_objects ) + elif magic_int in RUSTPYTHON_MAGICS: + from xdis.unmarsh_rust import VersionIndependentUnmarshallerRust + um_gen = VersionIndependentUnmarshallerRust( + fp, magic_int, bytes_for_s, code_objects + ) else: um_gen = VersionIndependentUnmarshaller( fp, magic_int, bytes_for_s, code_objects=code_objects