diff --git a/xdis/codetype/__init__.py b/xdis/codetype/__init__.py index b9c71781..65708841 100644 --- a/xdis/codetype/__init__.py +++ b/xdis/codetype/__init__.py @@ -28,7 +28,6 @@ from xdis.codetype.code38 import Code38 from xdis.codetype.code310 import Code310 from xdis.codetype.code311 import Code311, Code311FieldNames -from xdis.codetype.code312 import Code312 from xdis.version_info import PYTHON_VERSION_TRIPLE @@ -101,7 +100,7 @@ def codeType2Portable(code, version_tuple=PYTHON_VERSION_TRIPLE): co_firstlineno=code.co_firstlineno, co_linetable=line_table, ) - elif version_tuple[:2] == (3,11): + elif version_tuple[:2] >= (3,11): return Code311( co_argcount=code.co_argcount, co_posonlyargcount=code.co_posonlyargcount, @@ -122,27 +121,6 @@ def codeType2Portable(code, version_tuple=PYTHON_VERSION_TRIPLE): co_linetable=line_table, co_exceptiontable=code.co_exceptiontable, ) - elif version_tuple[:2] >= (3,12): - return Code312( - co_argcount=code.co_argcount, - co_posonlyargcount=code.co_posonlyargcount, - co_kwonlyargcount=code.co_kwonlyargcount, - co_nlocals=code.co_nlocals, - co_stacksize=code.co_stacksize, - co_flags=code.co_flags, - co_consts=code.co_consts, - co_code=code.co_code, - co_names=code.co_names, - co_varnames=code.co_varnames, - co_freevars=code.co_freevars, - co_cellvars=code.co_cellvars, - co_filename=code.co_filename, - co_name=code.co_name, - co_qualname=code.co_qualname, - co_firstlineno=code.co_firstlineno, - co_linetable=line_table, - co_exceptiontable=code.co_exceptiontable, - ) elif version_tuple > (2, 0): # 2.0 .. 2.7 return Code2( @@ -209,11 +187,9 @@ def portableCodeType(version_tuple=PYTHON_VERSION_TRIPLE): elif version_tuple[:2] == (3, 10): # 3.10 return Code310 - elif version_tuple[:2] == (3,11): + elif version_tuple[:2] >= (3,11): # 3.11 ... return Code311 - elif version_tuple[:2] >= (3,12): - return Code312 elif version_tuple > (2, 0): # 2.0 .. 2.7 return Code2 diff --git a/xdis/codetype/code310.py b/xdis/codetype/code310.py index 78b4437b..d79c4fd8 100644 --- a/xdis/codetype/code310.py +++ b/xdis/codetype/code310.py @@ -17,6 +17,8 @@ import types from copy import deepcopy +import struct + from xdis.codetype.code38 import Code38 from xdis.cross_types import UnicodeForPython3 from xdis.version_info import PYTHON_VERSION_TRIPLE, version_tuple_to_str @@ -160,41 +162,25 @@ def co_lines(self): equal to the size of the bytecode. line will either be a positive integer, or None + Parsing implementation adapted from: https://github.com/python/cpython/blob/3.10/Objects/lnotab_notes.txt """ - line_number = self.co_firstlineno - start_offset = 0 - byte_increments = [c for c in tuple(self.co_linetable[0::2])] - line_deltas = [c for c in tuple(self.co_linetable[1::2])] - - if len(byte_increments) == 0: - yield start_offset, len(self.co_code), line_number - return - - byte_incr = byte_increments[0] - line_delta = line_deltas[0] - assert line_delta != -128, "the first line delta can't logically be -128" - assert isinstance(line_delta, int) - if line_delta > 127: - line_delta = 256 - line_delta - else: - line_number += line_delta - - for byte_incr, line_delta in zip(byte_increments[1:], line_deltas[1:]): + line = self.co_firstlineno + end_offset = 0 + # co_linetable is pairs of (offset_delta: unsigned byte, line_delta: signed byte) + for offset_delta, line_delta in struct.iter_unpack('=Bb', self.co_linetable): assert isinstance(line_delta, int) - assert isinstance(byte_incr, int) - end_offset = start_offset + byte_incr - if line_delta > 127: - line_delta = 256 - line_delta - - if line_delta == -128: - line_delta = 0 - yield start_offset, end_offset, line_number - line_number += line_delta + assert isinstance(offset_delta, int) + if line_delta == 0: # No change to line number, just accumulate changes to end + end_offset += offset_delta + continue start_offset = end_offset - - end_offset = len(self.co_code) - yield start_offset, end_offset, line_number - return + end_offset = start_offset + offset_delta + if line_delta == -128: # No valid line number -- skip entry + continue + line += line_delta + if end_offset == start_offset: # Empty range, omit. + continue + yield start_offset, end_offset, line def encode_lineno_tab(self): """ diff --git a/xdis/codetype/code311.py b/xdis/codetype/code311.py index 9e406e0d..18e3d7c9 100644 --- a/xdis/codetype/code311.py +++ b/xdis/codetype/code311.py @@ -23,6 +23,37 @@ from xdis.version_info import PYTHON_VERSION_TRIPLE, version_tuple_to_str +# Note: order is the positional order given in the Python docs for +# 3.11 types.Codetype. +# "posonlyargcount" is not used, but it is in other Python versions, so it +# has to be included since this structure is used as the Union type +# for all code types. +Code311FieldNames = """ + co_argcount + co_posonlyargcount + co_kwonlyargcount + co_nlocals + co_stacksize + co_flags + co_consts + co_code + co_names + co_varnames + co_freevars + co_cellvars + co_filename + co_name + co_qualname + co_firstlineno + co_linetable + co_exceptiontable +""" + +Code311FieldTypes = deepcopy(Code310FieldTypes) +Code311FieldTypes.update({"co_qualname": str, "co_exceptiontable": bytes}) + + +##### Parse location table ##### def parse_location_entries(location_bytes, first_line): """ Parses the locations table described in: https://github.com/python/cpython/blob/3.11/Objects/locations.md @@ -131,36 +162,6 @@ def decode_signed_varint(s): return entries -# Note: order is the positional order given in the Python docs for -# 3.11 types.Codetype. -# "posonlyargcount" is not used, but it is in other Python versions, so it -# has to be included since this structure is used as the Union type -# for all code types. -Code311FieldNames = """ - co_argcount - co_posonlyargcount - co_kwonlyargcount - co_nlocals - co_stacksize - co_flags - co_consts - co_code - co_names - co_varnames - co_freevars - co_cellvars - co_filename - co_name - co_qualname - co_firstlineno - co_linetable - co_exceptiontable -""" - -Code311FieldTypes = deepcopy(Code310FieldTypes) -Code311FieldTypes.update({"co_qualname": str, "co_exceptiontable": bytes}) - - ##### NEW "OPAQUE" LINE TABLE PARSING ##### # See: https://github.com/python/cpython/blob/aaed91cabcedc16c089c4b1c9abb1114659a83d3/Objects/codeobject.c#L1245C1-L1245C17 PY_CODE_LOCATION_INFO_SHORT0 = 0 diff --git a/xdis/codetype/code312.py b/xdis/codetype/code312.py deleted file mode 100644 index 1f37257f..00000000 --- a/xdis/codetype/code312.py +++ /dev/null @@ -1,97 +0,0 @@ -import types -from copy import deepcopy - -from xdis.codetype.code311 import Code311, Code311FieldTypes -from xdis.version_info import PYTHON_VERSION_TRIPLE, version_tuple_to_str - -Code312FieldNames = Code311FieldTypes.copy() -Code312FieldTypes = deepcopy(Code311FieldTypes) - - -class Code312(Code311): - """Class for a Python 3.13+ code object - New CPython "undocumented" changes make this necessary to parse the co_linetable with co_lines(). - See: https://github.com/python/cpython/blob/aaed91cabcedc16c089c4b1c9abb1114659a83d3/Objects/codeobject.c#L1245C1-L1245C17 - """ - - def __init__( - self, - co_argcount, - co_posonlyargcount, - co_kwonlyargcount, - co_nlocals, - co_stacksize, - co_flags, - co_consts, - co_code, - co_names, - co_varnames, - co_freevars, - co_cellvars, - co_filename, - co_name, - co_qualname, - co_firstlineno, - co_linetable, - co_exceptiontable, - ): - # Keyword argument parameters in the call below is more robust. - # Since things change around, robustness is good. - super(Code312, self).__init__( - co_argcount=co_argcount, - co_posonlyargcount=co_posonlyargcount, - co_kwonlyargcount=co_kwonlyargcount, - co_nlocals=co_nlocals, - co_stacksize=co_stacksize, - co_flags=co_flags, - co_consts=co_consts, - co_code=co_code, - co_names=co_names, - co_varnames=co_varnames, - co_freevars=co_freevars, - co_cellvars=co_cellvars, - co_filename=co_filename, - co_name=co_name, - co_qualname=co_qualname, - co_firstlineno=co_firstlineno, - co_linetable=co_linetable, - co_exceptiontable=co_exceptiontable, - ) - self.fieldtypes = Code312FieldTypes - if type(self) == Code312: - self.check() - - def to_native(self): - if not (PYTHON_VERSION_TRIPLE >= (3, 12)): - raise TypeError( - "Python Interpreter needs to be in 3.12 or greater; is %s" - % version_tuple_to_str() - ) - - code = deepcopy(self) - code.freeze() - try: - code.check() - except AssertionError as e: - raise TypeError(e) - - return types.CodeType( - code.co_argcount, - code.co_posonlyargcount, - code.co_kwonlyargcount, - code.co_nlocals, - code.co_stacksize, - code.co_flags, - code.co_code, - code.co_consts, - code.co_names, - code.co_varnames, - code.co_filename, - code.co_name, - code.co_qualname, - code.co_firstlineno, - code.co_linetable, - code.co_exceptiontable, - code.co_freevars, - code.co_cellvars, - ) diff --git a/xdis/opcodes/opcode_310.py b/xdis/opcodes/opcode_310.py index e97f387d..c4f0dc4f 100644 --- a/xdis/opcodes/opcode_310.py +++ b/xdis/opcodes/opcode_310.py @@ -49,15 +49,10 @@ def_op(loc, "RERAISE", 119, 3, 0) def_op(loc, "GEN_START", 129, 1, 0) def_op(loc, "MATCH_CLASS", 152, 2, 1) -# fmt: on - +# fmt: on opcode_arg_fmt = opcode_arg_fmt310 = opcode_arg_fmt39.copy() opcode_extended_fmt = opcode_extended_fmt310 = opcode_extended_fmt39.copy() -# fmt: on - -opcode_arg_fmt = opcode_arg_fmt10 = opcode_arg_fmt39.copy() - update_pj3(globals(), loc) finalize_opcodes(loc) diff --git a/xdis/opcodes/opcode_311.py b/xdis/opcodes/opcode_311.py index f07f4885..eeb54db4 100644 --- a/xdis/opcodes/opcode_311.py +++ b/xdis/opcodes/opcode_311.py @@ -297,10 +297,6 @@ def format_BINARY_OP(arg) -> str: del opcode_extended_fmt311["INPLACE_TRUE_DIVIDE"] del opcode_extended_fmt311["INPLACE_XOR"] -opcode_extended_fmt = opcode_extended_fmt311 - -opcode_arg_fmt = opcode_arg_fmt11 = opcode_arg_fmt310.copy() - from xdis.opcodes.opcode_310 import findlinestarts update_pj3(globals(), loc) diff --git a/xdis/opcodes/opcode_312.py b/xdis/opcodes/opcode_312.py index 29d695ae..ccbbb37e 100644 --- a/xdis/opcodes/opcode_312.py +++ b/xdis/opcodes/opcode_312.py @@ -177,6 +177,10 @@ def format_CALL_INTRINSIC_1(arg) -> str: def format_CALL_INTRINSIC_2(arg) -> str: return _intrinsic_2_descs[arg] + +opcode_extended_fmt = opcode_extended_fmt312 = opcode_extended_fmt311.copy() +opcode_arg_fmt = opcode_arg_fmt12 = opcode_arg_fmt311.copy() + ### update arg formatting opcode_arg_fmt312 = { **opcode_arg_fmt311, @@ -185,9 +189,6 @@ def format_CALL_INTRINSIC_2(arg) -> str: "CALL_INTRINSIC_2": format_CALL_INTRINSIC_2, }, } -opcode_extended_fmt = opcode_extended_fmt312 = opcode_extended_fmt311.copy() - -opcode_arg_fmt = opcode_arg_fmt12 = opcode_arg_fmt311.copy() from xdis.opcodes.opcode_311 import findlinestarts diff --git a/xdis/opcodes/opcode_313.py b/xdis/opcodes/opcode_313.py index 25797eb8..808cdd89 100644 --- a/xdis/opcodes/opcode_313.py +++ b/xdis/opcodes/opcode_313.py @@ -450,8 +450,8 @@ opcode_extended_fmt = opcode_312.opcode_extended_fmt312.copy() for fmt_table in (opcode_arg_fmt, opcode_extended_fmt): fmt_table.pop("MAKE_FUNCTION") # MAKE_FUNCTION formatting not in 3.13 -opcode_arg_fmt13 = opcode_arg_fmt -opcode_extended_fmt13 = opcode_extended_fmt +opcode_arg_fmt313 = opcode_arg_fmt +opcode_extended_fmt313 = opcode_extended_fmt # update any calls to findlinestarts to include the version tuple