def check_stack_effect(): import dis from xdis import IS_PYPY from xdis.op_imports import get_opcode_module if IS_PYPY: variant = "pypy" else: variant = "" opc = get_opcode_module(None, variant) for ( opname, opcode, ) in opc.opmap.items(): if opname in ("EXTENDED_ARG", "NOP"): continue xdis_args = [opcode, opc] dis_args = [opcode] if op_has_argument(opcode, opc): xdis_args.append(0) dis_args.append(0) if ( PYTHON_VERSION_TRIPLE > (3, 7) and opcode in opc.CONDITION_OPS and opname not in ( "JUMP_IF_FALSE_OR_POP", "JUMP_IF_TRUE_OR_POP", "POP_JUMP_IF_FALSE", "POP_JUMP_IF_TRUE", "SETUP_FINALLY", ) ): xdis_args.append(0) dis_args.append(0) effect = xstack_effect(*xdis_args) check_effect = dis.stack_effect(*dis_args) if effect == -100: print( "%d (%s) needs adjusting; should be: should have effect %d" % (opcode, opname, check_effect) ) elif check_effect == effect: pass # print("%d (%s) is good: effect %d" % (opcode, opname, effect)) else: print( "%d (%s) not okay; effect %d vs %d" % (opcode, opname, effect, check_effect) ) pass pass return
def lineoffsets_in_file(filename, toplevel_only=False): obj_path = check_object_path(filename) version, timestamp, magic_int, code, pypy, source_size, sip_hash = load_module( obj_path) if pypy: variant = "pypy" else: variant = None opc = get_opcode_module(version, variant) return LineOffsetInfo(opc, code, not toplevel_only) pass
def __init__(self, python_version=sys.version_info, variant=VARIANT): if python_version >= (3, 6): import xdis.wordcode as xcode else: import xdis.bytecode as xcode self.xcode = xcode self.opc = opc = get_opcode_module(python_version, variant) self.python_version = opc.version self.python_version_tuple = opc.version_tuple self.is_pypy = variant == PYPY self.hasconst = opc.hasconst self.hasname = opc.hasname self.opmap = opc.opmap self.opname = opc.opname self.EXTENDED_ARG = opc.EXTENDED_ARG self.HAVE_ARGUMENT = opc.HAVE_ARGUMENT class Bytecode(_Bytecode): """The bytecode operations of a piece of code Instantiate this with a function, method, string of code, or a code object (as returned by compile()). Iterating over this yields the bytecode operations as Instruction instances. """ def __init__(self, x, first_line=None, current_offset=None): super(Bytecode, self).__init__(x, opc=opc, first_line=first_line, current_offset=current_offset) self.Bytecode = Bytecode class Instruction(_Instruction): """Details for a bytecode operation Defined fields: opname - human readable name for operation opcode - numeric code for operation arg - numeric argument to operation (if any), otherwise None argval - resolved arg value (if known), otherwise same as arg argrepr - human readable description of operation argument offset - start index of operation within bytecode sequence starts_line - line started by this opcode (if any), otherwise None is_jump_target - True if other code jumps to here, otherwise False """ def __init__(self, *args, **kwargs): _Instruction(*args, **kwargs) self.opc = opc self.Instruction = Instruction
def __init__(self, python_version=sys.version_info, variant=VARIANT): if python_version >= (3, 6): import xdis.wordcode as xcode else: import xdis.bytecode as xcode self.xcode = xcode self.opc = opc = get_opcode_module(python_version, variant) self.python_version = opc.version self.is_pypy = variant == PYPY self.hasconst = opc.hasconst self.hasname = opc.hasname self.opmap = opc.opmap self.opname = opc.opname self.EXTENDED_ARG = opc.EXTENDED_ARG self.HAVE_ARGUMENT = opc.HAVE_ARGUMENT class Bytecode(_Bytecode): """The bytecode operations of a piece of code Instantiate this with a function, method, string of code, or a code object (as returned by compile()). Iterating over this yields the bytecode operations as Instruction instances. """ def __init__(self, x, first_line=None, current_offset=None): super(Bytecode, self).__init__(x, opc=opc, first_line=first_line, current_offset=current_offset) self.Bytecode = Bytecode class Instruction(_Instruction): """Details for a bytecode operation Defined fields: opname - human readable name for operation opcode - numeric code for operation arg - numeric argument to operation (if any), otherwise None argval - resolved arg value (if known), otherwise same as arg argrepr - human readable description of operation argument offset - start index of operation within bytecode sequence starts_line - line started by this opcode (if any), otherwise None is_jump_target - True if other code jumps to here, otherwise False """ def __init__(self, *args, **kwargs): _Instruction(*args, **kwargs) self.opc = opc self.Instruction = Instruction
def __init__( self, python_version=PYTHON_VERSION, is_pypy=IS_PYPY, vmtest_testing=False, format_instruction_func=format_instruction, ): # The call stack of frames. self.frames = [] # The current frame. self.frame = None self.return_value = None self.last_exception = None self.last_traceback_limit = None self.last_traceback = None self.version = python_version self.is_pypy = is_pypy self.format_instruction = format_instruction_func # FIXME: until we figure out how to fix up test/vmtest.el # This changes how we report a VMRuntime error. self.vmtest_testing = vmtest_testing # Like sys.exc_info() tuple self.last_exception = None # Sometimes we need a native function (e.g. for method lookup), but # most of the time we want a VM function defined in pyobj. # This maps between the two. self.fn2native = {} self.in_exception_processing = False # This is somewhat hoaky: # Give byteop routines a way to raise an error, without having # to import this file. We import from from byteops. # Alternatively, VMError could be # pulled out of this file self.PyVMError = PyVMError int_vers = int(python_version * 10) version_info = (int_vers // 10, int_vers % 10) variant = "pypy" if is_pypy else None self.opc = get_opcode_module(version_info, variant) self.byteop = get_byteop(self, python_version, is_pypy)
def test_inst_size(): if (PYTHON_VERSION_TRIPLE[:2] == (3, 6)) and not IS_PYPY: opc = get_opcode_module(sys.version_info) bytecode_obj = Bytecode(extended_arg_fn36, opc) instructions = list(bytecode_obj.get_instructions(extended_arg_fn36)) inst1 = instructions[1] assert inst1.opname == 'EXTENDED_ARG' assert inst1.argval == 0 inst2 = instructions[2] assert inst2.opname == 'POP_JUMP_IF_FALSE' assert inst2.has_extended_arg == True assert inst2.inst_size == 4 # for inst in instructions: # print(inst) else: assert True
def test_inst_jumps(): if (sys.version_info >= (2, 7)): variant = 'pypy' if IS_PYPY else None opc = get_opcode_module(sys.version_info, variant) bytecode_obj = Bytecode(extended_arg_fn36, opc) instructions = list(bytecode_obj.get_instructions(loop)) seen_pjif = False seen_ja = False for inst in instructions: if inst.opname == "POP_JUMP_IF_FALSE": assert inst.is_jump() seen_pjif = True elif inst.opname == "JUMP_ABSOLUTE": assert inst.is_jump() assert not inst.jumps_forward() seen_ja = True pass pass assert seen_pjif assert seen_ja
def test_inst_size(): if (sys.version_info == (3, 6)): variant = 'pypy' if IS_PYPY else None opc = get_opcode_module(sys.version_info, variant) bytecode_obj = Bytecode(extended_arg_fn36, opc) instructions = list(bytecode_obj.get_instructions(extended_arg_fn36)) inst1 = instructions[1] assert inst1.opname == 'EXTENDED_ARG' assert inst1.argval == 0 inst2 = instructions[2] assert inst2.opname == 'POP_JUMP_IF_FALSE' assert inst2.has_extended_arg == True assert inst2.inst_size == 4 # for inst in instructions: # print(inst) else: assert True
def test_inst_size(): if (sys.version_info == (3,6)): variant = 'pypy' if IS_PYPY else None opc = get_opcode_module(sys.version_info, variant) bytecode_obj = Bytecode(extended_arg_fn36, opc) instructions = list(bytecode_obj.get_instructions(extended_arg_fn36)) inst1 = instructions[1] assert inst1.opname == 'EXTENDED_ARG' assert inst1.argval == 0 inst2 = instructions[2] assert inst2.opname == 'POP_JUMP_IF_FALSE' assert inst2.has_extended_arg == True assert inst2.inst_size == 4 # for inst in instructions: # print(inst) else: assert True
def test_inst_jumps(): if (sys.version_info >= (2, 7)): variant = 'pypy' if IS_PYPY else None opc = get_opcode_module(sys.version_info, variant) bytecode_obj = Bytecode(extended_arg_fn36, opc) instructions = list(bytecode_obj.get_instructions(loop)) seen_pjif = False seen_ja = False for inst in instructions: if inst.opname == "POP_JUMP_IF_FALSE": assert inst.is_jump() seen_pjif = True elif inst.opname == "JUMP_ABSOLUTE": assert inst.is_jump() assert not inst.jumps_forward() seen_ja = True pass pass assert seen_pjif # Python 3.10 code generation is more efficient and doesn't # and removes a JUMP_ABSOLUTE. if PYTHON_VERSION_TRIPLE < (3, 10): assert seen_ja
def test_stack_effect_vs_dis(): if xdis.PYTHON_VERSION < 3.4 or xdis.IS_PYPY: # TODO figure out some other kind if internal checks to tod. print("Skipped for now - need to figure out how to test") return def test_one(xdis_args, dis_args, has_arg): effect = xstack_effect(*xdis_args) check_effect = dis.stack_effect(*dis_args) assert effect != -100, ( "%d (%s) needs adjusting; should be: should have effect %d" % (opcode, opname, check_effect)) if has_arg: op_val = "with operand %d" % dis_args[1] else: op_val = "" assert check_effect == effect, ( "%d (%s) %s not okay; effect %d vs %d" % (opcode, opname, op_val, effect, check_effect)) print("%d (%s) is good: effect %d" % (opcode, opname, effect)) if xdis.IS_PYPY: variant = "pypy" else: variant = "" opc = get_opcode_module(None, variant) for opname, opcode, in opc.opmap.items(): if opname in ("EXTENDED_ARG", "NOP"): continue xdis_args = [opcode, opc] dis_args = [opcode] # TODO: if opcode takes an argument, we should vary the arg and try # values in addition to 0 as done below. if op_has_argument(opcode, opc): xdis_args.append(0) dis_args.append(0) has_arg = True else: has_arg = False if (xdis.PYTHON_VERSION > 3.7 and opcode in opc.CONDITION_OPS and opname not in ( "JUMP_IF_FALSE_OR_POP", "JUMP_IF_TRUE_OR_POP", "POP_JUMP_IF_FALSE", "POP_JUMP_IF_TRUE", "SETUP_FINALLY", )): xdis_args.append(0) dis_args.append(0) if has_arg: for i in range(0, 3): dis_args[1] = xdis_args[2] = i test_one(xdis_args, dis_args, has_arg) pass pass else: test_one(xdis_args, dis_args, has_arg) pass return
def test_findlabels(): code = findlabels.__code__.co_code opc = get_opcode_module() assert findlabels(code, opc) == findlabels_std(code)
effect = xstack_effect(*xdis_args) check_effect = dis.stack_effect(*dis_args) if effect == -100: print( "%d (%s) needs adjusting; should be: should have effect %d" % (opcode, opname, check_effect) ) elif check_effect == effect: pass # print("%d (%s) is good: effect %d" % (opcode, opname, effect)) else: print( "%d (%s) not okay; effect %d vs %d" % (opcode, opname, effect, check_effect) ) pass pass return if __name__ == "__main__": from dis import findlabels as findlabels_std code = findlabels.__code__.co_code from xdis.op_imports import get_opcode_module opc = get_opcode_module() assert findlabels(code, opc) == findlabels_std(code) if PYTHON_VERSION_TRIPLE >= (3, 4): check_stack_effect()
def __init__(self, version=PYTHON_VERSION_TRIPLE, is_pypy=IS_PYPY): global end_bb end_bb = 0 self.bb_list = [] self.exit_block = None version = version[:2] self.opcode = opcode = get_opcode_module(version) self.EXCEPT_INSTRUCTIONS = set([opcode.opmap["POP_TOP"]]) self.FINALLY_INSTRUCTIONS = set([opcode.opmap["SETUP_FINALLY"]]) self.FOR_INSTRUCTIONS = set([opcode.opmap["FOR_ITER"]]) self.JABS_INSTRUCTIONS = set(opcode.hasjabs) self.JREL_INSTRUCTIONS = set(opcode.hasjrel) self.JUMP_INSTRUCTIONS = self.JABS_INSTRUCTIONS | self.JREL_INSTRUCTIONS self.JUMP_UNCONDITONAL = set( [opcode.opmap["JUMP_ABSOLUTE"], opcode.opmap["JUMP_FORWARD"]] ) self.POP_BLOCK_INSTRUCTIONS = set([opcode.opmap["POP_BLOCK"]]) self.RETURN_INSTRUCTIONS = set([opcode.opmap["RETURN_VALUE"]]) # These instructions don't appear in all version of Python self.BREAK_INSTRUCTIONS = set() self.END_FINALLY_INSTRUCTIONS = set() self.LOOP_INSTRUCTIONS = set() self.TRY_INSTRUCTIONS = set() self.END_FINALLY_INSTRUCTIONS = set() self.LOOP_INSTRUCTIONS = set() self.TRY_INSTRUCTIONS = set() if version < (3, 10): if version < (3, 8): self.BREAK_INSTRUCTIONS = set([opcode.opmap["BREAK_LOOP"]]) self.LOOP_INSTRUCTIONS = set([opcode.opmap["SETUP_LOOP"]]) self.TRY_INSTRUCTIONS = set([opcode.opmap["SETUP_EXCEPT"]]) elif version < (3, 9): # FIXME: add WITH_EXCEPT_START self.END_FINALLY_INSTRUCTIONS = set([opcode.opmap["END_FINALLY"]]) pass else: self.EXCEPT_INSTRUCTIONS = set([opcode.opmap["RAISE_VARARGS"]]) if version >= (2, 6): self.JUMP_CONDITONAL = set( [ opcode.opmap["POP_JUMP_IF_FALSE"], opcode.opmap["POP_JUMP_IF_TRUE"], opcode.opmap["JUMP_IF_FALSE_OR_POP"], opcode.opmap["JUMP_IF_TRUE_OR_POP"], ] ) self.NOFOLLOW_INSTRUCTIONS = set( [ opcode.opmap["RETURN_VALUE"], opcode.opmap["YIELD_VALUE"], opcode.opmap["RAISE_VARARGS"], ] ) # ?? # opcode.opmap['YIELD_VALUE'], # opcode.opmap['RAISE_VARARGS']]) if not PYTHON3: if version in ((2, 6), (2, 7)): # We classify intructions into various categories (even though # many of the below contain just one instruction). This can # isolate us from instruction changes in Python. # The classifications are used in setting basic block flag bits self.JUMP_UNCONDITONAL = set( [opcode.opmap["JUMP_ABSOLUTE"], opcode.opmap["JUMP_FORWARD"]] )
opcodes, supplying a custom file to dump the dis to) across python versions, for example: import xdis.std as dis # works in Python 2 and 3 for op in dis.Bytecode('for i in range(10): pass'): print(op) """ from xdis.op_imports import get_opcode_module # hasconst, hasname, etc. are used from the outside. Set them. __g = globals() opcodes = get_opcode_module() for field in "hasconst hasname opmap opname EXTENDED_ARG HAVE_ARGUMENT".split( ): __g[field] = getattr(opcodes, field) # std import sys # local from xdis import PYTHON_VERSION, IS_PYPY from xdis.bytecode import ( Bytecode as _Bytecode, Instruction as _Instruction, ) if PYTHON_VERSION >= 3.6: import xdis.wordcode as xcode else: