Skip to content

Commit bce2dd5

Browse files
authored
Merge pull request #183 from rocky/rust-python-opcode-generalize
Handle Rust bytecode (for 3.12 and most recent RustPython using CPython's 3.12 magic number)
2 parents b5cafe1 + bd33c17 commit bce2dd5

File tree

13 files changed

+1148
-702
lines changed

13 files changed

+1148
-702
lines changed

xdis/codetype/code312rust.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# (C) Copyright 2025 by Rocky Bernstein
2+
#
3+
# This program is free software; you can redistribute it and/or
4+
# modify it under the terms of the GNU General Public License
5+
# as published by the Free Software Foundation; either version 2
6+
# of the License, or (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program; if not, write to the Free Software
15+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16+
17+
from dataclasses import dataclass
18+
from typing import Any, Dict, Tuple, Union
19+
20+
from xdis.codetype.code311 import Code311
21+
22+
23+
@dataclass
24+
class SourceLocation:
25+
# 1-based integer line number
26+
line_number: int
27+
# column offset in line: 1-based int (constructed from a zero-indexed stored value)
28+
column_offset: int
29+
30+
31+
class Code312Rust(Code311):
32+
"""Class for a RustPython 3.12 code object used when a Python
33+
interpreter is not RustPython 3.12 but working on RustPython 3.12 bytecode. It
34+
also functions as an object that can be used to build or write a
35+
Python3 code object, since we allow mutable structures.
36+
37+
When done mutating, call method to_native().
38+
39+
For convenience in generating code objects, fields like
40+
`co_consts`, co_names which are (immutable) tuples in the end-result can be stored
41+
instead as (mutable) lists. Likewise, the line number table `co_lnotab`
42+
can be stored as a simple list of offset, line_number tuples.
43+
44+
"""
45+
def __init__(
46+
self,
47+
co_argcount: int,
48+
co_posonlyargcount: int,
49+
co_kwonlyargcount: int,
50+
co_stacksize: int,
51+
co_flags: int,
52+
co_code: bytes,
53+
co_consts: tuple,
54+
co_names: tuple[str],
55+
co_varnames: tuple[str],
56+
co_filename: str,
57+
co_name: str,
58+
co_firstlineno: int,
59+
co_freevars: tuple,
60+
co_cellvars: tuple,
61+
version_triple: Tuple[int, int, int],
62+
locations: tuple,
63+
collection_order: Dict[Union[set, frozenset, dict], Tuple[Any]] = {},
64+
) -> None:
65+
self.co_argcount = co_argcount
66+
self.co_posonlyargcount = co_posonlyargcount
67+
self.co_kwonlyargcount = co_kwonlyargcount
68+
self.co_lnotab={} # FIXME: compute this from locations.
69+
self.co_stacksize = co_stacksize
70+
self.co_flags = co_flags
71+
self.co_code = co_code
72+
self.co_consts = co_consts
73+
self.co_names = co_names
74+
self.co_varnames = co_varnames
75+
self.co_filename = co_filename
76+
self.co_name = co_name
77+
self.co_firstlineno = co_firstlineno # None if 0 in serialized form
78+
self.co_cellvars = co_cellvars
79+
self.co_freevars= co_freevars
80+
self.co_collection_order = collection_order
81+
self.version_triple = version_triple
82+
self.locations = locations

xdis/codetype/code313rust.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ def __init__(
8282
self.co_linetable = co_linetable
8383
self.co_qualname = co_qualname
8484
self.co_cellvars = co_cellvars
85-
self.co_linetable = co_linetable
8685
self.co_freevars= co_freevars
8786
self.co_exceptiontable = co_exceptiontable
8887
self.co_collection_order = collection_order

xdis/cross_dis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def findlinestarts(code, dup_lines: bool = False):
170170
Generate pairs (offset, lineno) as described in Python/compile.c.
171171
"""
172172

173-
if hasattr(code, "co_lines"):
173+
if hasattr(code, "co_lines") and hasattr(code, "co_linetable"):
174174
# Taken from 3.10 findlinestarts
175175
lastline = None
176176
for start, _, line in code.co_lines():
@@ -325,7 +325,7 @@ def format_code_info(
325325
lines.append("# Keyword-only arguments: %s" % co.co_kwonlyargcount)
326326

327327
pos_argc = co.co_argcount
328-
if version_tuple >= (1, 3):
328+
if hasattr(co, "co_nlocals"):
329329
lines.append("# Number of locals: %s" % co.co_nlocals)
330330
if version_tuple >= (1, 5):
331331
lines.append("# Stack size: %s" % co.co_stacksize)

xdis/load.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,24 @@ def load_module_from_file_object(
228228
magic = fp.read(4)
229229
magic_int = magic2int(magic)
230230

231+
if magic_int == 3531:
232+
# this magic int is used for both 3.12 and 3.13Rust!
233+
# Disambiguate using the fact that CPython 3.13 stores 0xe3
234+
# "c" | 0x80 at offoset 0x10 while RustPython uses "c" (no 0x80).
235+
fp.seek(0x10)
236+
code_type = fp.read(1)
237+
if code_type == b'c':
238+
# Is RustPython 3.13 using CPython's 3.12 magic number.
239+
magic_int = 35310
240+
else:
241+
assert code_type == b'\xe3', "Expecting magic int 3531 to have a code type b'0x63 or b'0x33' at offset 0x10"
242+
fp.seek(0x04)
243+
231244
# For reasons I don't understand, PyPy 3.2 stores a magic
232245
# of '0'... The two values below are for Python 2.x and 3.x respectively
233246
if magic[0:1] in ["0", b"0"]:
234247
magic = int2magic(3180 + 7)
248+
magic_int = magic2int(magic)
235249

236250
try:
237251
# FIXME: use the internal routine below
@@ -245,10 +259,7 @@ def load_module_from_file_object(
245259
else:
246260
raise ImportError(f"Bad magic number: '{magic}'")
247261

248-
if magic_int in [2657, 22138] + list(
249-
RUSTPYTHON_MAGICS
250-
) + list(JYTHON_MAGICS):
251-
version = magicint2version.get(magic_int, "")
262+
version = magic_int2tuple(magic_int)
252263

253264
if magic_int in INTERIM_MAGIC_INTS:
254265
raise ImportError(
@@ -268,8 +279,6 @@ def load_module_from_file_object(
268279

269280
try:
270281
my_magic_int = PYTHON_MAGIC_INT
271-
magic_int = magic2int(magic)
272-
version = magic_int2tuple(magic_int)
273282

274283
timestamp = None
275284
source_size = None

xdis/magics.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@
4747
# See below for mapping to version numbers.
4848
PYPY3_MAGICS = (48, 64, 112, 160, 192, 240, 244, 256, 320, 336, 384, 416)
4949

50+
# Rust Magics is a git commit number!
5051
RUSTPYTHON_MAGICS = (
5152
12641, # RustPython 3.12
52-
12897, # RustPython 3.12
53+
12897, # RustPython 3.12.0 0.4.0
5354
13413, # RustPython 3.13
54-
24881, # RustPython 3.13
55+
24881, # RustPython 3.13 0.4.0
56+
35310, # RustPython 3.13 0.4.0 later version! Actually 3531 is stored in file
5557
)
5658

5759
# A list of interim Python version magic numbers used, but were not
@@ -515,7 +517,7 @@ def __by_version(magic_versions: Dict[bytes, str]) -> dict:
515517
add_magic_from_int(3512, "3.12a2c")
516518
add_magic_from_int(3513, "3.12a4a")
517519
add_magic_from_int(3514, "3.12a4b")
518-
add_magic_from_int(3515, "3.12a5a")
520+
# add_magic_from_int(3515, "3.12a5a") # Rust Python 3.13 0.40 uses this too!
519521
add_magic_from_int(3516, "3.12a5b")
520522
add_magic_from_int(3517, "3.12a5c")
521523
add_magic_from_int(3518, "3.12a6a")
@@ -695,9 +697,12 @@ def __by_version(magic_versions: Dict[bytes, str]) -> dict:
695697
add_magic_from_int(416, "3.11.13PyPy") # PyPy 3.11.13 or pypy3.11-7.3.20
696698

697699
add_magic_from_int(12641, "3.12.0a.rust") # RustPython 3.12.0
698-
add_magic_from_int(12897, "3.13.0b.rust") # RustPython 3.12.0
700+
add_magic_from_int(12897, "3.12.0.rust") # RustPython 3.12.0 0.4.0
699701
add_magic_from_int(13413, "3.13.0a.rust") # RustPython 3.13.0
700-
add_magic_from_int(24881, "3.13.0b.rust") # RustPython 3.13.0
702+
add_magic_from_int(24881, "3.13.0b.rust") # RustPython 3.13.0 0.4.0
703+
704+
# Actually we should add 3531, but that already means CPython 3.12!
705+
add_magic_from_int(35310, "3.13.1.rust") # RustPython 3.13.0 0.4.0
701706

702707
# Graal Python. Graal uses its own JVM-ish CPython bytecode, not
703708
# true CPython or PyPy bytecode.

xdis/op_imports.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@
6060
opcode_311pypy,
6161
opcode_312,
6262
opcode_313,
63-
opcode_313rust,
6463
opcode_314,
64+
opcode_3531rust,
65+
opcode_12897rust,
66+
opcode_24481rust,
6567
)
6668
from xdis.opcodes.opcode_graal import (
6769
opcode_38graal,
@@ -187,10 +189,12 @@
187189
3.11: opcode_311,
188190
"3.12.7Graal": opcode_312graal, # this right?
189191
"3.12.8Graal": opcode_312graal, # this right?
192+
"3.12.0Rust": opcode_12897rust,
190193
"3.12.0rc2": opcode_312,
191194
"3.12.0": opcode_312,
192195
"3.13.0rc3": opcode_313,
193-
"3.13.0Rust": opcode_313rust,
196+
"3.13.0Rust": opcode_24481rust,
197+
"3.13.1Rust": opcode_3531rust,
194198
"3.14b3": opcode_314,
195199
"3.14.0": opcode_314,
196200
"3.14": opcode_314,
@@ -211,7 +215,7 @@ def get_opcode_module(version_info: Tuple[int, ...], implementation: PythonImple
211215
if vers_str not in canonic_python_version:
212216
vers_str = version_tuple_to_str(version_info[:2])
213217

214-
if implementation != PythonImplementation.CPython:
218+
if str(implementation) != str(PythonImplementation.CPython):
215219
vers_str += str(implementation)
216220

217221
return op_imports[canonic_python_version[vers_str]]
@@ -330,4 +334,4 @@ def remap_opcodes(op_obj, alternate_opmap):
330334

331335
if __name__ == "__main__":
332336
from version_info import PYTHON_IMPLEMENTATION, PYTHON_VERSION_TRIPLE
333-
print(get_opcode_module(PYTHON_VERSION_TRIPLE, PYTHON_IMPLEMENTATION))
337+
print(get_opcode_module(PYTHON_VERSION_TRIPLE[:2], PYTHON_IMPLEMENTATION))

xdis/opcodes/__init__.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
opcode_310pypy,
6969
opcode_311pypy,
7070
)
71-
from xdis.opcodes.opcode_rust import opcode_313rust # opcode_312rust,
71+
from xdis.opcodes.opcode_rust import opcode_3531rust, opcode_12897rust, opcode_24481rust
7272

7373
# from xdis.opcodes.opcode_graal import (
7474
# opcode_310graal,
@@ -78,7 +78,6 @@
7878
# )
7979

8080

81-
8281
__all__ = [
8382
"opcode_10",
8483
"opcode_11",
@@ -123,9 +122,8 @@
123122
"opcode_39pypy",
124123
# "opcode_310graal",
125124
# "opcode_311graal",
126-
# "opcode_312graal",
127-
"opcode_38graal",
128125
# "opcode_312rust",
129-
"opcode_313rust",
130-
126+
"opcode_3531rust",
127+
"opcode_12897rust",
128+
"opcode_24481rust",
131129
]

0 commit comments

Comments
 (0)