Skip to content

Commit 1cab563

Browse files
author
rocky
committed
Add is_fixed_worksize_bytecode()
local Variable is_python36 changed to is_fixed_wordsize_bytecode() function. This clarifies *why* this distinction is important. This function can be used outside of xdis (and is). Also, add more annotations.
1 parent 73bcf41 commit 1cab563

File tree

4 files changed

+38
-29
lines changed

4 files changed

+38
-29
lines changed

xdis/bin/pydisasm.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Mode: -*- python -*-
2-
# Copyright (c) 2015-2021 by Rocky Bernstein <[email protected]>
2+
# Copyright (c) 2015-2021, 2025 by Rocky Bernstein <[email protected]>
33
#
44
# Note: we can't start with #! because setup.py bdist_wheel will look for that
55
# and change that into something that's not portable. Thank you, Python!
@@ -10,6 +10,7 @@
1010
import os
1111
import os.path as osp
1212
import sys
13+
from typing import List
1314

1415
import click
1516

@@ -61,7 +62,7 @@
6162
)
6263
@click.version_option(version=__version__)
6364
@click.argument("files", nargs=-1, type=click.Path(readable=True), required=True)
64-
def main(format: list[str], method: tuple, show_source: bool, show_file_offsets, files):
65+
def main(format: List[str], method: tuple, show_source: bool, show_file_offsets, files):
6566
"""Disassembles a Python bytecode file.
6667
6768
We handle bytecode for virtually every release of Python and some releases of PyPy.

xdis/bytecode.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def get_const_info(const_index, const_list):
9292
_get_const_info = get_const_info
9393

9494

95-
def get_name_info(name_index, name_list):
95+
def get_name_info(name_index: int, name_list) -> tuple:
9696
"""Helper to get optional details about named references
9797
9898
Returns the dereferenced name as both value and repr if the name
@@ -142,11 +142,6 @@ def get_optype(opcode: int, opc) -> str:
142142

143143
return "??"
144144

145-
146-
# For compatibility
147-
_get_name_info = get_name_info
148-
149-
150145
def offset2line(offset: int, linestarts):
151146
"""linestarts is expected to be a *list of (offset, line number)
152147
where both offset and line number are in increasing order.
@@ -189,7 +184,7 @@ def _parse_varint(iterator: Iterator[int]) -> int:
189184
)
190185

191186

192-
def parse_exception_table(exception_table: bytes):
187+
def parse_exception_table(exception_table: bytes) -> list:
193188
iterator = iter(exception_table)
194189
entries = []
195190
try:
@@ -222,6 +217,13 @@ def prefer_double_quote(string: str) -> str:
222217
return f'"{string[1:-1]}"'
223218
return string
224219

220+
def is_fixed_wordsize_bytecode(opc) -> bool:
221+
"""
222+
Returns True if intructions in opc are fixed length (2 bytes)
223+
Python byte code instructions before to 3.6 was one or three bytes.
224+
3.6 and after, instructions were fixed at 2 bytes.
225+
"""
226+
return True if opc.python_version >= (3, 6) else False
225227

226228
def get_logical_instruction_at_offset(
227229
bytecode,
@@ -248,15 +250,17 @@ def get_logical_instruction_at_offset(
248250
# PERFORMANCE FIX: Only add exception labels if we're building labels ourselves
249251
# When called from get_instructions_bytes, labels already includes exception targets
250252
if exception_entries is not None:
251-
for start, end, target, _, _ in exception_entries:
253+
for _start, _end, target, _, _ in exception_entries:
252254
if target not in labels:
253255
labels.append(target)
254256

255257
# label_maps = get_jump_target_maps(bytecode, opc)
256258

257259
# FIXME: We really need to distinguish 3.6.0a1 from 3.6.a3.
258-
# See below FIXME
259-
python_36 = True if opc.python_version >= (3, 6) else False
260+
# See below FIXME.
261+
# Python 3.6 starts fixed-length bytecode of 2 bytes per instruction.
262+
# Before that and initially, bytecode was either 1 or 3 bytes.
263+
fixed_length_instructions = is_fixed_wordsize_bytecode(opc)
260264

261265
starts_line = None
262266

@@ -295,7 +299,7 @@ def get_logical_instruction_at_offset(
295299
argrepr = ""
296300
has_arg = op_has_argument(op, opc)
297301
if has_arg:
298-
if python_36:
302+
if fixed_length_instructions:
299303
arg = code2num(bytecode, i) | extended_arg
300304
extended_arg = (arg << 8) if opname == "EXTENDED_ARG" else 0
301305
# FIXME: Python 3.6.0a1 is 2, for 3.6.a3 we have 1
@@ -325,19 +329,19 @@ def get_logical_instruction_at_offset(
325329
argval, argrepr = _get_const_info(arg, constants)
326330
elif op in opc.NAME_OPS:
327331
if opc.version_tuple >= (3, 11) and opname == "LOAD_GLOBAL":
328-
argval, argrepr = _get_name_info(arg >> 1, names)
332+
argval, argrepr = get_name_info(arg >> 1, names)
329333
if arg & 1:
330334
argrepr = "NULL + " + argrepr
331335
elif opc.version_tuple >= (3, 12) and opname == "LOAD_ATTR":
332-
argval, argrepr = _get_name_info(arg >> 1, names)
336+
argval, argrepr = get_name_info(arg >> 1, names)
333337
if arg & 1:
334338
argrepr = "NULL|self + " + argrepr
335339
elif opc.version_tuple >= (3, 12) and opname == "LOAD_SUPER_ATTR":
336-
argval, argrepr = _get_name_info(arg >> 2, names)
340+
argval, argrepr = get_name_info(arg >> 2, names)
337341
if arg & 1:
338342
argrepr = "NULL|self + " + argrepr
339343
else:
340-
argval, argrepr = _get_name_info(arg, names)
344+
argval, argrepr = get_name_info(arg, names)
341345
elif op in opc.JREL_OPS:
342346
signed_arg = -arg if "JUMP_BACKWARD" in opname else arg
343347
argval = i + get_jump_val(signed_arg, opc.python_version)
@@ -368,19 +372,19 @@ def get_logical_instruction_at_offset(
368372
):
369373
arg1 = arg >> 4
370374
arg2 = arg & 15
371-
argval1, argrepr1 = _get_name_info(arg1, localsplusnames)
372-
argval2, argrepr2 = _get_name_info(arg2, localsplusnames)
375+
argval1, argrepr1 = get_name_info(arg1, localsplusnames)
376+
argval2, argrepr2 = get_name_info(arg2, localsplusnames)
373377
argval = argval1, argval2
374378
argrepr = argrepr1 + ", " + argrepr2
375379
elif opc.version_tuple >= (3, 11):
376-
argval, argrepr = _get_name_info(arg, localsplusnames)
380+
argval, argrepr = get_name_info(arg, localsplusnames)
377381
else:
378-
argval, argrepr = _get_name_info(arg, varnames)
382+
argval, argrepr = get_name_info(arg, varnames)
379383
elif op in opc.FREE_OPS:
380384
if opc.version_tuple >= (3, 11):
381-
argval, argrepr = _get_name_info(arg, localsplusnames)
385+
argval, argrepr = get_name_info(arg, localsplusnames)
382386
else:
383-
argval, argrepr = _get_name_info(arg, cells)
387+
argval, argrepr = get_name_info(arg, cells)
384388
elif op in opc.COMPARE_OPS:
385389
if opc.python_version >= (3, 13):
386390
# The fifth-lowest bit of the oparg now indicates a forced conversion to bool.
@@ -392,15 +396,18 @@ def get_logical_instruction_at_offset(
392396
argrepr = argval
393397
elif op in opc.NARGS_OPS:
394398
opname = opname
395-
if python_36 and opname in ("CALL_FUNCTION", "CALL_FUNCTION_EX"):
399+
if fixed_length_instructions and opname in (
400+
"CALL_FUNCTION",
401+
"CALL_FUNCTION_EX",
402+
):
396403
if opname == "CALL_FUNCTION":
397404
argrepr = format_CALL_FUNCTION(code2num(bytecode, i - 1))
398405
else:
399406
assert opname == "CALL_FUNCTION_EX"
400407
argrepr = format_CALL_FUNCTION_EX(code2num(bytecode, i - 1))
401408
else:
402409
if not (
403-
python_36
410+
fixed_length_instructions
404411
or opname in ("RAISE_VARARGS", "DUP_TOPX", "MAKE_FUNCTION")
405412
):
406413
argrepr = "%d positional, %d named" % (
@@ -410,7 +417,7 @@ def get_logical_instruction_at_offset(
410417
if hasattr(opc, "opcode_arg_fmt") and opname in opc.opcode_arg_fmt:
411418
argrepr = opc.opcode_arg_fmt[opname](arg)
412419
else:
413-
if python_36:
420+
if fixed_length_instructions:
414421
i += 1
415422
if hasattr(opc, "opcode_arg_fmt") and opname in opc.opcode_arg_fmt:
416423
argrepr = opc.opcode_arg_fmt[opname](arg)

xdis/magics.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import struct
3737
import sys
3838
from importlib.util import MAGIC_NUMBER as MAGIC
39-
from typing import Dict, Set
39+
from typing import Dict, Set, Tuple
4040

4141
from xdis.version_info import IS_GRAAL, IS_PYPY, IS_RUST, version_tuple_to_str
4242

@@ -799,7 +799,7 @@ def magic_int2tuple(magic_int: int) -> tuple:
799799
return py_str2tuple(magicint2version[magic_int])
800800

801801

802-
def py_str2tuple(orig_version: str) -> tuple[int, int] | tuple[int, int, int]:
802+
def py_str2tuple(orig_version: str) -> Tuple[int, int] | Tuple[int, int, int]:
803803
"""Convert a Python version into a tuple number,
804804
e.g. (2, 5), (3, 6).
805805

xdis/version_info.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import platform
2020
import sys
21+
from typing import Tuple
2122

2223
PYTHON3 = sys.version_info >= (3, 0)
2324

@@ -30,7 +31,7 @@
3031

3132

3233
def version_tuple_to_str(
33-
version_tuple: tuple[int, ...]=PYTHON_VERSION_TRIPLE, start: int=0, end: int=3, delimiter: str="."
34+
version_tuple: Tuple[int, ...]=PYTHON_VERSION_TRIPLE, start: int=0, end: int=3, delimiter: str="."
3435
) -> str:
3536
"""
3637
Turn a version tuple, e.g. (3,2,6), into a dotted string, e.g. "3.2.6".

0 commit comments

Comments
 (0)