# (C) Copyright 2016-2017, 2019-2021, 2023-2024 # 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.7 bytecode opcodes This is like Python 3.7's opcode.py with some classification of stack usage and information for formatting instructions. """ from typing import Optional, Tuple import xdis.opcodes.opcode_36 as opcode_36 from xdis.opcodes.base import ( call_op, def_op, finalize_opcodes, init_opdata, jrel_op, name_op, rm_op, update_pj3, ) from xdis.opcodes.format.extended import ( extended_format_CALL_METHOD, get_instruction_arg, ) from xdis.opcodes.opcode_36 import ( format_CALL_FUNCTION, opcode_arg_fmt36, opcode_extended_fmt36, ) version_tuple = (3, 7) python_implementation = "CPython" loc = locals() init_opdata(loc, opcode_36, version_tuple) ### Opcodes that have changed drastically #### # BUILD_MAP_UNPACK_WITH_CALL oparg # oparg is the number of unpacked mappings which no longer limited by # 255. As in BUILD_MAP_UNPACK.The location of the function is `oparg + # 2`. # CALL_FUNCTION oparg # oparg is the number of positional arguments on the stack which no # longer limited by 255. # CALL_FUNCTION_KW oparg # This is a different opcode from before, with the name of a similar # opcode as before. It s like CALL_FUNCTION but values of keyword # arguments are pushed on the stack after values of positional # arguments, and then a constant tuple of keyword names is pushed on # the top of the stack. *oparg* is the sum of numbers of positional # and keyword arguments. The number of keyword arguments is # determined by the length of the tuple of keyword names. # CALL_FUNCTION_EX** takes 2 to 3 arguments on the stack: the # function, the tuple of positional arguments, and optionally the dict # of keyword arguments if bit 0 of *oparg* is 1. # MAKE_FUNCTION oparg # This is a different opcode from before. # The tuple of default values for positional-or-keyword parameters, # the dict of default values for keyword-only parameters, the dict of # annotations and the closure are pushed on the stack if corresponding # bit (0-3) is set. They are followed by the code object and the # qualified name of the function. # These are removed since 3.6... # and STORE_ANNOTATION introduced in 3.6! rm_op(loc, "STORE_ANNOTATION", 127) # fmt: off # These have a changed stack effect since 3.6 # OP NAME OPCODE POP PUSH #--------------------------------------------------------------- def_op(loc, "WITH_CLEANUP_START", 81, 0, 2) def_op(loc, "WITH_CLEANUP_FINISH", 82, 3, 0) def_op(loc, "END_FINALLY", 88, 6, 0) def_op(loc, "POP_EXCEPT", 89, 3, 0) # Pops last 3 values jrel_op(loc, "SETUP_WITH", 143, 0, 6) jrel_op(loc, "SETUP_ASYNC_WITH", 154, 0, 5) # These are new since Python 3.7 name_op(loc, "LOAD_METHOD", 160, 0, 1) call_op(loc, "CALL_METHOD", 161, -2, 1) format_MAKE_FUNCTION_36 = opcode_36.format_MAKE_FUNCTION_36 format_value_flags = opcode_36.format_value_flags # fmt: on def extended_format_LOAD_METHOD(opc, instructions: list) -> Tuple[str, Optional[int]]: instr1 = instructions[1] if instr1.tos_str or instr1.opcode in opc.nullaryloadop: base = get_instruction_arg(instr1) return ( f"{base}.{instructions[0].argrepr}", instr1.start_offset, ) return "", None def extended_format_RAISE_VARARGS(opc, instructions) -> Tuple[Optional[str], int]: raise_inst = instructions[0] assert raise_inst.opname == "RAISE_VARARGS" argc = raise_inst.argval start_offset = raise_inst.start_offset if argc == 0: return "reraise", start_offset elif argc == 1: exception_name_inst = instructions[1] start_offset = exception_name_inst.start_offset exception_name = ( exception_name_inst.tos_str if exception_name_inst.tos_str else exception_name_inst.argrepr ) if exception_name is not None: return f"raise {exception_name}()", start_offset return format_RAISE_VARARGS(raise_inst.argval), start_offset def format_RAISE_VARARGS(argc) -> str | None: assert 0 <= argc <= 2 if argc == 0: return "reraise" elif argc == 1: return "exception instance" elif argc == 2: return "exception instance with __cause__" opcode_arg_fmt = opcode_arg_fmt37 = { **opcode_arg_fmt36, **{"CALL_METHOD": format_CALL_FUNCTION, "RAISE_VARARGS": format_RAISE_VARARGS}, } opcode_extended_fmt = opcode_extended_fmt37 = { **opcode_extended_fmt36, **{ "CALL_METHOD": extended_format_CALL_METHOD, "LOAD_METHOD": extended_format_LOAD_METHOD, "RAISE_VARARGS": extended_format_RAISE_VARARGS, }, } update_pj3(globals(), loc) finalize_opcodes(loc)