From fb44bc9dee20ef6ed04a2e6a9196b109eafcdbdc Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 17 Sep 2025 10:53:47 +0200 Subject: [PATCH 01/16] Update `opcode` from 3.13.7 --- Lib/_opcode_metadata.py | 343 ++++++++++++++++++++++++++++++ Lib/opcode.py | 449 ++++++---------------------------------- 2 files changed, 401 insertions(+), 391 deletions(-) create mode 100644 Lib/_opcode_metadata.py diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py new file mode 100644 index 0000000000..b3d7b8103e --- /dev/null +++ b/Lib/_opcode_metadata.py @@ -0,0 +1,343 @@ +# This file is generated by Tools/cases_generator/py_metadata_generator.py +# from: +# Python/bytecodes.c +# Do not edit! +_specializations = { + "RESUME": [ + "RESUME_CHECK", + ], + "TO_BOOL": [ + "TO_BOOL_ALWAYS_TRUE", + "TO_BOOL_BOOL", + "TO_BOOL_INT", + "TO_BOOL_LIST", + "TO_BOOL_NONE", + "TO_BOOL_STR", + ], + "BINARY_OP": [ + "BINARY_OP_MULTIPLY_INT", + "BINARY_OP_ADD_INT", + "BINARY_OP_SUBTRACT_INT", + "BINARY_OP_MULTIPLY_FLOAT", + "BINARY_OP_ADD_FLOAT", + "BINARY_OP_SUBTRACT_FLOAT", + "BINARY_OP_ADD_UNICODE", + "BINARY_OP_INPLACE_ADD_UNICODE", + ], + "BINARY_SUBSCR": [ + "BINARY_SUBSCR_DICT", + "BINARY_SUBSCR_GETITEM", + "BINARY_SUBSCR_LIST_INT", + "BINARY_SUBSCR_STR_INT", + "BINARY_SUBSCR_TUPLE_INT", + ], + "STORE_SUBSCR": [ + "STORE_SUBSCR_DICT", + "STORE_SUBSCR_LIST_INT", + ], + "SEND": [ + "SEND_GEN", + ], + "UNPACK_SEQUENCE": [ + "UNPACK_SEQUENCE_TWO_TUPLE", + "UNPACK_SEQUENCE_TUPLE", + "UNPACK_SEQUENCE_LIST", + ], + "STORE_ATTR": [ + "STORE_ATTR_INSTANCE_VALUE", + "STORE_ATTR_SLOT", + "STORE_ATTR_WITH_HINT", + ], + "LOAD_GLOBAL": [ + "LOAD_GLOBAL_MODULE", + "LOAD_GLOBAL_BUILTIN", + ], + "LOAD_SUPER_ATTR": [ + "LOAD_SUPER_ATTR_ATTR", + "LOAD_SUPER_ATTR_METHOD", + ], + "LOAD_ATTR": [ + "LOAD_ATTR_INSTANCE_VALUE", + "LOAD_ATTR_MODULE", + "LOAD_ATTR_WITH_HINT", + "LOAD_ATTR_SLOT", + "LOAD_ATTR_CLASS", + "LOAD_ATTR_PROPERTY", + "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", + "LOAD_ATTR_METHOD_WITH_VALUES", + "LOAD_ATTR_METHOD_NO_DICT", + "LOAD_ATTR_METHOD_LAZY_DICT", + "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", + ], + "COMPARE_OP": [ + "COMPARE_OP_FLOAT", + "COMPARE_OP_INT", + "COMPARE_OP_STR", + ], + "CONTAINS_OP": [ + "CONTAINS_OP_SET", + "CONTAINS_OP_DICT", + ], + "FOR_ITER": [ + "FOR_ITER_LIST", + "FOR_ITER_TUPLE", + "FOR_ITER_RANGE", + "FOR_ITER_GEN", + ], + "CALL": [ + "CALL_BOUND_METHOD_EXACT_ARGS", + "CALL_PY_EXACT_ARGS", + "CALL_TYPE_1", + "CALL_STR_1", + "CALL_TUPLE_1", + "CALL_BUILTIN_CLASS", + "CALL_BUILTIN_O", + "CALL_BUILTIN_FAST", + "CALL_BUILTIN_FAST_WITH_KEYWORDS", + "CALL_LEN", + "CALL_ISINSTANCE", + "CALL_LIST_APPEND", + "CALL_METHOD_DESCRIPTOR_O", + "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", + "CALL_METHOD_DESCRIPTOR_NOARGS", + "CALL_METHOD_DESCRIPTOR_FAST", + "CALL_ALLOC_AND_ENTER_INIT", + "CALL_PY_GENERAL", + "CALL_BOUND_METHOD_GENERAL", + "CALL_NON_PY_GENERAL", + ], +} + +_specialized_opmap = { + 'BINARY_OP_ADD_FLOAT': 150, + 'BINARY_OP_ADD_INT': 151, + 'BINARY_OP_ADD_UNICODE': 152, + 'BINARY_OP_INPLACE_ADD_UNICODE': 3, + 'BINARY_OP_MULTIPLY_FLOAT': 153, + 'BINARY_OP_MULTIPLY_INT': 154, + 'BINARY_OP_SUBTRACT_FLOAT': 155, + 'BINARY_OP_SUBTRACT_INT': 156, + 'BINARY_SUBSCR_DICT': 157, + 'BINARY_SUBSCR_GETITEM': 158, + 'BINARY_SUBSCR_LIST_INT': 159, + 'BINARY_SUBSCR_STR_INT': 160, + 'BINARY_SUBSCR_TUPLE_INT': 161, + 'CALL_ALLOC_AND_ENTER_INIT': 162, + 'CALL_BOUND_METHOD_EXACT_ARGS': 163, + 'CALL_BOUND_METHOD_GENERAL': 164, + 'CALL_BUILTIN_CLASS': 165, + 'CALL_BUILTIN_FAST': 166, + 'CALL_BUILTIN_FAST_WITH_KEYWORDS': 167, + 'CALL_BUILTIN_O': 168, + 'CALL_ISINSTANCE': 169, + 'CALL_LEN': 170, + 'CALL_LIST_APPEND': 171, + 'CALL_METHOD_DESCRIPTOR_FAST': 172, + 'CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS': 173, + 'CALL_METHOD_DESCRIPTOR_NOARGS': 174, + 'CALL_METHOD_DESCRIPTOR_O': 175, + 'CALL_NON_PY_GENERAL': 176, + 'CALL_PY_EXACT_ARGS': 177, + 'CALL_PY_GENERAL': 178, + 'CALL_STR_1': 179, + 'CALL_TUPLE_1': 180, + 'CALL_TYPE_1': 181, + 'COMPARE_OP_FLOAT': 182, + 'COMPARE_OP_INT': 183, + 'COMPARE_OP_STR': 184, + 'CONTAINS_OP_DICT': 185, + 'CONTAINS_OP_SET': 186, + 'FOR_ITER_GEN': 187, + 'FOR_ITER_LIST': 188, + 'FOR_ITER_RANGE': 189, + 'FOR_ITER_TUPLE': 190, + 'LOAD_ATTR_CLASS': 191, + 'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 192, + 'LOAD_ATTR_INSTANCE_VALUE': 193, + 'LOAD_ATTR_METHOD_LAZY_DICT': 194, + 'LOAD_ATTR_METHOD_NO_DICT': 195, + 'LOAD_ATTR_METHOD_WITH_VALUES': 196, + 'LOAD_ATTR_MODULE': 197, + 'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 198, + 'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 199, + 'LOAD_ATTR_PROPERTY': 200, + 'LOAD_ATTR_SLOT': 201, + 'LOAD_ATTR_WITH_HINT': 202, + 'LOAD_GLOBAL_BUILTIN': 203, + 'LOAD_GLOBAL_MODULE': 204, + 'LOAD_SUPER_ATTR_ATTR': 205, + 'LOAD_SUPER_ATTR_METHOD': 206, + 'RESUME_CHECK': 207, + 'SEND_GEN': 208, + 'STORE_ATTR_INSTANCE_VALUE': 209, + 'STORE_ATTR_SLOT': 210, + 'STORE_ATTR_WITH_HINT': 211, + 'STORE_SUBSCR_DICT': 212, + 'STORE_SUBSCR_LIST_INT': 213, + 'TO_BOOL_ALWAYS_TRUE': 214, + 'TO_BOOL_BOOL': 215, + 'TO_BOOL_INT': 216, + 'TO_BOOL_LIST': 217, + 'TO_BOOL_NONE': 218, + 'TO_BOOL_STR': 219, + 'UNPACK_SEQUENCE_LIST': 220, + 'UNPACK_SEQUENCE_TUPLE': 221, + 'UNPACK_SEQUENCE_TWO_TUPLE': 222, +} + +opmap = { + 'CACHE': 0, + 'RESERVED': 17, + 'RESUME': 149, + 'INSTRUMENTED_LINE': 254, + 'BEFORE_ASYNC_WITH': 1, + 'BEFORE_WITH': 2, + 'BINARY_SLICE': 4, + 'BINARY_SUBSCR': 5, + 'CHECK_EG_MATCH': 6, + 'CHECK_EXC_MATCH': 7, + 'CLEANUP_THROW': 8, + 'DELETE_SUBSCR': 9, + 'END_ASYNC_FOR': 10, + 'END_FOR': 11, + 'END_SEND': 12, + 'EXIT_INIT_CHECK': 13, + 'FORMAT_SIMPLE': 14, + 'FORMAT_WITH_SPEC': 15, + 'GET_AITER': 16, + 'GET_ANEXT': 18, + 'GET_ITER': 19, + 'GET_LEN': 20, + 'GET_YIELD_FROM_ITER': 21, + 'INTERPRETER_EXIT': 22, + 'LOAD_ASSERTION_ERROR': 23, + 'LOAD_BUILD_CLASS': 24, + 'LOAD_LOCALS': 25, + 'MAKE_FUNCTION': 26, + 'MATCH_KEYS': 27, + 'MATCH_MAPPING': 28, + 'MATCH_SEQUENCE': 29, + 'NOP': 30, + 'POP_EXCEPT': 31, + 'POP_TOP': 32, + 'PUSH_EXC_INFO': 33, + 'PUSH_NULL': 34, + 'RETURN_GENERATOR': 35, + 'RETURN_VALUE': 36, + 'SETUP_ANNOTATIONS': 37, + 'STORE_SLICE': 38, + 'STORE_SUBSCR': 39, + 'TO_BOOL': 40, + 'UNARY_INVERT': 41, + 'UNARY_NEGATIVE': 42, + 'UNARY_NOT': 43, + 'WITH_EXCEPT_START': 44, + 'BINARY_OP': 45, + 'BUILD_CONST_KEY_MAP': 46, + 'BUILD_LIST': 47, + 'BUILD_MAP': 48, + 'BUILD_SET': 49, + 'BUILD_SLICE': 50, + 'BUILD_STRING': 51, + 'BUILD_TUPLE': 52, + 'CALL': 53, + 'CALL_FUNCTION_EX': 54, + 'CALL_INTRINSIC_1': 55, + 'CALL_INTRINSIC_2': 56, + 'CALL_KW': 57, + 'COMPARE_OP': 58, + 'CONTAINS_OP': 59, + 'CONVERT_VALUE': 60, + 'COPY': 61, + 'COPY_FREE_VARS': 62, + 'DELETE_ATTR': 63, + 'DELETE_DEREF': 64, + 'DELETE_FAST': 65, + 'DELETE_GLOBAL': 66, + 'DELETE_NAME': 67, + 'DICT_MERGE': 68, + 'DICT_UPDATE': 69, + 'ENTER_EXECUTOR': 70, + 'EXTENDED_ARG': 71, + 'FOR_ITER': 72, + 'GET_AWAITABLE': 73, + 'IMPORT_FROM': 74, + 'IMPORT_NAME': 75, + 'IS_OP': 76, + 'JUMP_BACKWARD': 77, + 'JUMP_BACKWARD_NO_INTERRUPT': 78, + 'JUMP_FORWARD': 79, + 'LIST_APPEND': 80, + 'LIST_EXTEND': 81, + 'LOAD_ATTR': 82, + 'LOAD_CONST': 83, + 'LOAD_DEREF': 84, + 'LOAD_FAST': 85, + 'LOAD_FAST_AND_CLEAR': 86, + 'LOAD_FAST_CHECK': 87, + 'LOAD_FAST_LOAD_FAST': 88, + 'LOAD_FROM_DICT_OR_DEREF': 89, + 'LOAD_FROM_DICT_OR_GLOBALS': 90, + 'LOAD_GLOBAL': 91, + 'LOAD_NAME': 92, + 'LOAD_SUPER_ATTR': 93, + 'MAKE_CELL': 94, + 'MAP_ADD': 95, + 'MATCH_CLASS': 96, + 'POP_JUMP_IF_FALSE': 97, + 'POP_JUMP_IF_NONE': 98, + 'POP_JUMP_IF_NOT_NONE': 99, + 'POP_JUMP_IF_TRUE': 100, + 'RAISE_VARARGS': 101, + 'RERAISE': 102, + 'RETURN_CONST': 103, + 'SEND': 104, + 'SET_ADD': 105, + 'SET_FUNCTION_ATTRIBUTE': 106, + 'SET_UPDATE': 107, + 'STORE_ATTR': 108, + 'STORE_DEREF': 109, + 'STORE_FAST': 110, + 'STORE_FAST_LOAD_FAST': 111, + 'STORE_FAST_STORE_FAST': 112, + 'STORE_GLOBAL': 113, + 'STORE_NAME': 114, + 'SWAP': 115, + 'UNPACK_EX': 116, + 'UNPACK_SEQUENCE': 117, + 'YIELD_VALUE': 118, + 'INSTRUMENTED_RESUME': 236, + 'INSTRUMENTED_END_FOR': 237, + 'INSTRUMENTED_END_SEND': 238, + 'INSTRUMENTED_RETURN_VALUE': 239, + 'INSTRUMENTED_RETURN_CONST': 240, + 'INSTRUMENTED_YIELD_VALUE': 241, + 'INSTRUMENTED_LOAD_SUPER_ATTR': 242, + 'INSTRUMENTED_FOR_ITER': 243, + 'INSTRUMENTED_CALL': 244, + 'INSTRUMENTED_CALL_KW': 245, + 'INSTRUMENTED_CALL_FUNCTION_EX': 246, + 'INSTRUMENTED_INSTRUCTION': 247, + 'INSTRUMENTED_JUMP_FORWARD': 248, + 'INSTRUMENTED_JUMP_BACKWARD': 249, + 'INSTRUMENTED_POP_JUMP_IF_TRUE': 250, + 'INSTRUMENTED_POP_JUMP_IF_FALSE': 251, + 'INSTRUMENTED_POP_JUMP_IF_NONE': 252, + 'INSTRUMENTED_POP_JUMP_IF_NOT_NONE': 253, + 'JUMP': 256, + 'JUMP_NO_INTERRUPT': 257, + 'LOAD_CLOSURE': 258, + 'LOAD_METHOD': 259, + 'LOAD_SUPER_METHOD': 260, + 'LOAD_ZERO_SUPER_ATTR': 261, + 'LOAD_ZERO_SUPER_METHOD': 262, + 'POP_BLOCK': 263, + 'SETUP_CLEANUP': 264, + 'SETUP_FINALLY': 265, + 'SETUP_WITH': 266, + 'STORE_FAST_MAYBE_NULL': 267, +} + +HAVE_ARGUMENT = 44 +MIN_INSTRUMENTED_OPCODE = 236 diff --git a/Lib/opcode.py b/Lib/opcode.py index ab6b765b4b..5735686fa7 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -4,404 +4,47 @@ operate on bytecodes (e.g. peephole optimizers). """ -__all__ = ["cmp_op", "hasarg", "hasconst", "hasname", "hasjrel", "hasjabs", - "haslocal", "hascompare", "hasfree", "hasexc", "opname", "opmap", - "HAVE_ARGUMENT", "EXTENDED_ARG"] -# It's a chicken-and-egg I'm afraid: -# We're imported before _opcode's made. -# With exception unheeded -# (stack_effect is not needed) -# Both our chickens and eggs are allayed. -# --Larry Hastings, 2013/11/23 +__all__ = ["cmp_op", "stack_effect", "hascompare", "opname", "opmap", + "HAVE_ARGUMENT", "EXTENDED_ARG", "hasarg", "hasconst", "hasname", + "hasjump", "hasjrel", "hasjabs", "hasfree", "haslocal", "hasexc"] -try: - from _opcode import stack_effect - __all__.append('stack_effect') -except ImportError: - pass +import _opcode +from _opcode import stack_effect -cmp_op = ('<', '<=', '==', '!=', '>', '>=') - -hasarg = [] -hasconst = [] -hasname = [] -hasjrel = [] -hasjabs = [] -haslocal = [] -hascompare = [] -hasfree = [] -hasexc = [] - -def is_pseudo(op): - return op >= MIN_PSEUDO_OPCODE and op <= MAX_PSEUDO_OPCODE - -oplists = [hasarg, hasconst, hasname, hasjrel, hasjabs, - haslocal, hascompare, hasfree, hasexc] - -opmap = {} - -## pseudo opcodes (used in the compiler) mapped to the values -## they can become in the actual code. -_pseudo_ops = {} - -def def_op(name, op): - opmap[name] = op - -def name_op(name, op): - def_op(name, op) - hasname.append(op) - -def jrel_op(name, op): - def_op(name, op) - hasjrel.append(op) - -def jabs_op(name, op): - def_op(name, op) - hasjabs.append(op) - -def pseudo_op(name, op, real_ops): - def_op(name, op) - _pseudo_ops[name] = real_ops - # add the pseudo opcode to the lists its targets are in - for oplist in oplists: - res = [opmap[rop] in oplist for rop in real_ops] - if any(res): - assert all(res) - oplist.append(op) - - -# Instruction opcodes for compiled code -# Blank lines correspond to available opcodes - -def_op('CACHE', 0) -def_op('POP_TOP', 1) -def_op('PUSH_NULL', 2) - -def_op('NOP', 9) -def_op('UNARY_POSITIVE', 10) -def_op('UNARY_NEGATIVE', 11) -def_op('UNARY_NOT', 12) - -def_op('UNARY_INVERT', 15) - -def_op('BINARY_SUBSCR', 25) -def_op('BINARY_SLICE', 26) -def_op('STORE_SLICE', 27) - -def_op('GET_LEN', 30) -def_op('MATCH_MAPPING', 31) -def_op('MATCH_SEQUENCE', 32) -def_op('MATCH_KEYS', 33) - -def_op('PUSH_EXC_INFO', 35) -def_op('CHECK_EXC_MATCH', 36) -def_op('CHECK_EG_MATCH', 37) - -def_op('WITH_EXCEPT_START', 49) -def_op('GET_AITER', 50) -def_op('GET_ANEXT', 51) -def_op('BEFORE_ASYNC_WITH', 52) -def_op('BEFORE_WITH', 53) -def_op('END_ASYNC_FOR', 54) -def_op('CLEANUP_THROW', 55) - -def_op('STORE_SUBSCR', 60) -def_op('DELETE_SUBSCR', 61) - -# TODO: RUSTPYTHON -# Delete below def_op after updating coroutines.py -def_op('YIELD_FROM', 72) - -def_op('GET_ITER', 68) -def_op('GET_YIELD_FROM_ITER', 69) -def_op('PRINT_EXPR', 70) -def_op('LOAD_BUILD_CLASS', 71) - -def_op('LOAD_ASSERTION_ERROR', 74) -def_op('RETURN_GENERATOR', 75) - -def_op('LIST_TO_TUPLE', 82) -def_op('RETURN_VALUE', 83) -def_op('IMPORT_STAR', 84) -def_op('SETUP_ANNOTATIONS', 85) - -def_op('ASYNC_GEN_WRAP', 87) -def_op('PREP_RERAISE_STAR', 88) -def_op('POP_EXCEPT', 89) - -HAVE_ARGUMENT = 90 # real opcodes from here have an argument: - -name_op('STORE_NAME', 90) # Index in name list -name_op('DELETE_NAME', 91) # "" -def_op('UNPACK_SEQUENCE', 92) # Number of tuple items -jrel_op('FOR_ITER', 93) -def_op('UNPACK_EX', 94) -name_op('STORE_ATTR', 95) # Index in name list -name_op('DELETE_ATTR', 96) # "" -name_op('STORE_GLOBAL', 97) # "" -name_op('DELETE_GLOBAL', 98) # "" -def_op('SWAP', 99) -def_op('LOAD_CONST', 100) # Index in const list -hasconst.append(100) -name_op('LOAD_NAME', 101) # Index in name list -def_op('BUILD_TUPLE', 102) # Number of tuple items -def_op('BUILD_LIST', 103) # Number of list items -def_op('BUILD_SET', 104) # Number of set items -def_op('BUILD_MAP', 105) # Number of dict entries -name_op('LOAD_ATTR', 106) # Index in name list -def_op('COMPARE_OP', 107) # Comparison operator -hascompare.append(107) -name_op('IMPORT_NAME', 108) # Index in name list -name_op('IMPORT_FROM', 109) # Index in name list -jrel_op('JUMP_FORWARD', 110) # Number of words to skip -jrel_op('JUMP_IF_FALSE_OR_POP', 111) # Number of words to skip -jrel_op('JUMP_IF_TRUE_OR_POP', 112) # "" -jrel_op('POP_JUMP_IF_FALSE', 114) -jrel_op('POP_JUMP_IF_TRUE', 115) -name_op('LOAD_GLOBAL', 116) # Index in name list -def_op('IS_OP', 117) -def_op('CONTAINS_OP', 118) -def_op('RERAISE', 119) -def_op('COPY', 120) -def_op('BINARY_OP', 122) -jrel_op('SEND', 123) # Number of bytes to skip -def_op('LOAD_FAST', 124) # Local variable number, no null check -haslocal.append(124) -def_op('STORE_FAST', 125) # Local variable number -haslocal.append(125) -def_op('DELETE_FAST', 126) # Local variable number -haslocal.append(126) -def_op('LOAD_FAST_CHECK', 127) # Local variable number -haslocal.append(127) -jrel_op('POP_JUMP_IF_NOT_NONE', 128) -jrel_op('POP_JUMP_IF_NONE', 129) -def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3) -def_op('GET_AWAITABLE', 131) -def_op('MAKE_FUNCTION', 132) # Flags -def_op('BUILD_SLICE', 133) # Number of items -jrel_op('JUMP_BACKWARD_NO_INTERRUPT', 134) # Number of words to skip (backwards) -def_op('MAKE_CELL', 135) -hasfree.append(135) -def_op('LOAD_CLOSURE', 136) -hasfree.append(136) -def_op('LOAD_DEREF', 137) -hasfree.append(137) -def_op('STORE_DEREF', 138) -hasfree.append(138) -def_op('DELETE_DEREF', 139) -hasfree.append(139) -jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards) - -def_op('CALL_FUNCTION_EX', 142) # Flags - -def_op('EXTENDED_ARG', 144) -EXTENDED_ARG = 144 -def_op('LIST_APPEND', 145) -def_op('SET_ADD', 146) -def_op('MAP_ADD', 147) -def_op('LOAD_CLASSDEREF', 148) -hasfree.append(148) -def_op('COPY_FREE_VARS', 149) -def_op('YIELD_VALUE', 150) -def_op('RESUME', 151) # This must be kept in sync with deepfreeze.py -def_op('MATCH_CLASS', 152) - -def_op('FORMAT_VALUE', 155) -def_op('BUILD_CONST_KEY_MAP', 156) -def_op('BUILD_STRING', 157) - -def_op('LIST_EXTEND', 162) -def_op('SET_UPDATE', 163) -def_op('DICT_MERGE', 164) -def_op('DICT_UPDATE', 165) - -def_op('CALL', 171) -def_op('KW_NAMES', 172) -hasconst.append(172) +from _opcode_metadata import (_specializations, _specialized_opmap, opmap, + HAVE_ARGUMENT, MIN_INSTRUMENTED_OPCODE) +EXTENDED_ARG = opmap['EXTENDED_ARG'] - -hasarg.extend([op for op in opmap.values() if op >= HAVE_ARGUMENT]) - -MIN_PSEUDO_OPCODE = 256 - -pseudo_op('SETUP_FINALLY', 256, ['NOP']) -hasexc.append(256) -pseudo_op('SETUP_CLEANUP', 257, ['NOP']) -hasexc.append(257) -pseudo_op('SETUP_WITH', 258, ['NOP']) -hasexc.append(258) -pseudo_op('POP_BLOCK', 259, ['NOP']) - -pseudo_op('JUMP', 260, ['JUMP_FORWARD', 'JUMP_BACKWARD']) -pseudo_op('JUMP_NO_INTERRUPT', 261, ['JUMP_FORWARD', 'JUMP_BACKWARD_NO_INTERRUPT']) - -pseudo_op('LOAD_METHOD', 262, ['LOAD_ATTR']) - -MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1 - -del def_op, name_op, jrel_op, jabs_op, pseudo_op - -opname = ['<%r>' % (op,) for op in range(MAX_PSEUDO_OPCODE + 1)] +opname = ['<%r>' % (op,) for op in range(max(opmap.values()) + 1)] for op, i in opmap.items(): opname[i] = op +cmp_op = ('<', '<=', '==', '!=', '>', '>=') -_nb_ops = [ - ("NB_ADD", "+"), - ("NB_AND", "&"), - ("NB_FLOOR_DIVIDE", "//"), - ("NB_LSHIFT", "<<"), - ("NB_MATRIX_MULTIPLY", "@"), - ("NB_MULTIPLY", "*"), - ("NB_REMAINDER", "%"), - ("NB_OR", "|"), - ("NB_POWER", "**"), - ("NB_RSHIFT", ">>"), - ("NB_SUBTRACT", "-"), - ("NB_TRUE_DIVIDE", "/"), - ("NB_XOR", "^"), - ("NB_INPLACE_ADD", "+="), - ("NB_INPLACE_AND", "&="), - ("NB_INPLACE_FLOOR_DIVIDE", "//="), - ("NB_INPLACE_LSHIFT", "<<="), - ("NB_INPLACE_MATRIX_MULTIPLY", "@="), - ("NB_INPLACE_MULTIPLY", "*="), - ("NB_INPLACE_REMAINDER", "%="), - ("NB_INPLACE_OR", "|="), - ("NB_INPLACE_POWER", "**="), - ("NB_INPLACE_RSHIFT", ">>="), - ("NB_INPLACE_SUBTRACT", "-="), - ("NB_INPLACE_TRUE_DIVIDE", "/="), - ("NB_INPLACE_XOR", "^="), -] +# These lists are documented as part of the dis module's API +hasarg = [op for op in opmap.values() if _opcode.has_arg(op)] +hasconst = [op for op in opmap.values() if _opcode.has_const(op)] +hasname = [op for op in opmap.values() if _opcode.has_name(op)] +hasjump = [op for op in opmap.values() if _opcode.has_jump(op)] +hasjrel = hasjump # for backward compatibility +hasjabs = [] +hasfree = [op for op in opmap.values() if _opcode.has_free(op)] +haslocal = [op for op in opmap.values() if _opcode.has_local(op)] +hasexc = [op for op in opmap.values() if _opcode.has_exc(op)] -_specializations = { - "BINARY_OP": [ - "BINARY_OP_ADAPTIVE", - "BINARY_OP_ADD_FLOAT", - "BINARY_OP_ADD_INT", - "BINARY_OP_ADD_UNICODE", - "BINARY_OP_INPLACE_ADD_UNICODE", - "BINARY_OP_MULTIPLY_FLOAT", - "BINARY_OP_MULTIPLY_INT", - "BINARY_OP_SUBTRACT_FLOAT", - "BINARY_OP_SUBTRACT_INT", - ], - "BINARY_SUBSCR": [ - "BINARY_SUBSCR_ADAPTIVE", - "BINARY_SUBSCR_DICT", - "BINARY_SUBSCR_GETITEM", - "BINARY_SUBSCR_LIST_INT", - "BINARY_SUBSCR_TUPLE_INT", - ], - "CALL": [ - "CALL_ADAPTIVE", - "CALL_PY_EXACT_ARGS", - "CALL_PY_WITH_DEFAULTS", - "CALL_BOUND_METHOD_EXACT_ARGS", - "CALL_BUILTIN_CLASS", - "CALL_BUILTIN_FAST_WITH_KEYWORDS", - "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", - "CALL_NO_KW_BUILTIN_FAST", - "CALL_NO_KW_BUILTIN_O", - "CALL_NO_KW_ISINSTANCE", - "CALL_NO_KW_LEN", - "CALL_NO_KW_LIST_APPEND", - "CALL_NO_KW_METHOD_DESCRIPTOR_FAST", - "CALL_NO_KW_METHOD_DESCRIPTOR_NOARGS", - "CALL_NO_KW_METHOD_DESCRIPTOR_O", - "CALL_NO_KW_STR_1", - "CALL_NO_KW_TUPLE_1", - "CALL_NO_KW_TYPE_1", - ], - "COMPARE_OP": [ - "COMPARE_OP_ADAPTIVE", - "COMPARE_OP_FLOAT_JUMP", - "COMPARE_OP_INT_JUMP", - "COMPARE_OP_STR_JUMP", - ], - "EXTENDED_ARG": [ - "EXTENDED_ARG_QUICK", - ], - "FOR_ITER": [ - "FOR_ITER_ADAPTIVE", - "FOR_ITER_LIST", - "FOR_ITER_RANGE", - ], - "JUMP_BACKWARD": [ - "JUMP_BACKWARD_QUICK", - ], - "LOAD_ATTR": [ - "LOAD_ATTR_ADAPTIVE", - # These potentially push [NULL, bound method] onto the stack. - "LOAD_ATTR_CLASS", - "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", - "LOAD_ATTR_INSTANCE_VALUE", - "LOAD_ATTR_MODULE", - "LOAD_ATTR_PROPERTY", - "LOAD_ATTR_SLOT", - "LOAD_ATTR_WITH_HINT", - # These will always push [unbound method, self] onto the stack. - "LOAD_ATTR_METHOD_LAZY_DICT", - "LOAD_ATTR_METHOD_NO_DICT", - "LOAD_ATTR_METHOD_WITH_DICT", - "LOAD_ATTR_METHOD_WITH_VALUES", - ], - "LOAD_CONST": [ - "LOAD_CONST__LOAD_FAST", - ], - "LOAD_FAST": [ - "LOAD_FAST__LOAD_CONST", - "LOAD_FAST__LOAD_FAST", - ], - "LOAD_GLOBAL": [ - "LOAD_GLOBAL_ADAPTIVE", - "LOAD_GLOBAL_BUILTIN", - "LOAD_GLOBAL_MODULE", - ], - "RESUME": [ - "RESUME_QUICK", - ], - "STORE_ATTR": [ - "STORE_ATTR_ADAPTIVE", - "STORE_ATTR_INSTANCE_VALUE", - "STORE_ATTR_SLOT", - "STORE_ATTR_WITH_HINT", - ], - "STORE_FAST": [ - "STORE_FAST__LOAD_FAST", - "STORE_FAST__STORE_FAST", - ], - "STORE_SUBSCR": [ - "STORE_SUBSCR_ADAPTIVE", - "STORE_SUBSCR_DICT", - "STORE_SUBSCR_LIST_INT", - ], - "UNPACK_SEQUENCE": [ - "UNPACK_SEQUENCE_ADAPTIVE", - "UNPACK_SEQUENCE_LIST", - "UNPACK_SEQUENCE_TUPLE", - "UNPACK_SEQUENCE_TWO_TUPLE", - ], -} -_specialized_instructions = [ - opcode for family in _specializations.values() for opcode in family -] -_specialization_stats = [ - "success", - "failure", - "hit", - "deferred", - "miss", - "deopt", -] + +_intrinsic_1_descs = _opcode.get_intrinsic1_descs() +_intrinsic_2_descs = _opcode.get_intrinsic2_descs() +_nb_ops = _opcode.get_nb_ops() + +hascompare = [opmap["COMPARE_OP"]] _cache_format = { "LOAD_GLOBAL": { "counter": 1, "index": 1, - "module_keys_version": 2, + "module_keys_version": 1, "builtin_keys_version": 1, }, "BINARY_OP": { @@ -412,16 +55,19 @@ def pseudo_op(name, op, real_ops): }, "COMPARE_OP": { "counter": 1, - "mask": 1, + }, + "CONTAINS_OP": { + "counter": 1, }, "BINARY_SUBSCR": { "counter": 1, - "type_version": 2, - "func_version": 1, }, "FOR_ITER": { "counter": 1, }, + "LOAD_SUPER_ATTR": { + "counter": 1, + }, "LOAD_ATTR": { "counter": 1, "version": 2, @@ -436,13 +82,34 @@ def pseudo_op(name, op, real_ops): "CALL": { "counter": 1, "func_version": 2, - "min_args": 1, }, "STORE_SUBSCR": { "counter": 1, }, + "SEND": { + "counter": 1, + }, + "JUMP_BACKWARD": { + "counter": 1, + }, + "TO_BOOL": { + "counter": 1, + "version": 2, + }, + "POP_JUMP_IF_TRUE": { + "counter": 1, + }, + "POP_JUMP_IF_FALSE": { + "counter": 1, + }, + "POP_JUMP_IF_NONE": { + "counter": 1, + }, + "POP_JUMP_IF_NOT_NONE": { + "counter": 1, + }, } -_inline_cache_entries = [ - sum(_cache_format.get(opname[opcode], {}).values()) for opcode in range(256) -] +_inline_cache_entries = { + name : sum(value.values()) for (name, value) in _cache_format.items() +} From f14e623ba8a910b0601d254b0c789fe834cbad91 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 17 Sep 2025 10:54:10 +0200 Subject: [PATCH 02/16] Base `_opcode` --- compiler/core/src/bytecode.rs | 6 +++ stdlib/Cargo.toml | 2 +- stdlib/src/lib.rs | 2 + stdlib/src/opcode.rs | 83 +++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 stdlib/src/opcode.rs diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index c2ce4e52c0..cbe2673d07 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -294,6 +294,12 @@ impl OpArg { } } +impl From for OpArg { + fn from(raw: u32) -> Self { + Self(raw) + } +} + #[derive(Default, Copy, Clone)] #[repr(transparent)] pub struct OpArgState { diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index c65582ba72..fd6d3e8a59 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -22,7 +22,7 @@ tkinter = ["dep:tk-sys", "dep:tcl-sys"] [dependencies] # rustpython crates rustpython-derive = { workspace = true } -rustpython-vm = { workspace = true, default-features = false } +rustpython-vm = { workspace = true, default-features = false, features = ["compiler"]} rustpython-common = { workspace = true } ahash = { workspace = true } diff --git a/stdlib/src/lib.rs b/stdlib/src/lib.rs index c22a95d16c..706ce0ef21 100644 --- a/stdlib/src/lib.rs +++ b/stdlib/src/lib.rs @@ -38,6 +38,7 @@ mod locale; mod math; #[cfg(unix)] mod mmap; +mod opcode; mod pyexpat; mod pystruct; mod random; @@ -135,6 +136,7 @@ pub fn get_module_inits() -> impl Iterator, StdlibInit "_json" => json::make_module, "math" => math::make_module, "pyexpat" => pyexpat::make_module, + "_opcode" => opcode::make_module, "_random" => random::make_module, "_statistics" => statistics::make_module, "_struct" => pystruct::make_module, diff --git a/stdlib/src/opcode.rs b/stdlib/src/opcode.rs new file mode 100644 index 0000000000..2bb6cb429a --- /dev/null +++ b/stdlib/src/opcode.rs @@ -0,0 +1,83 @@ +pub(crate) use opcode::make_module; + +#[pymodule] +mod opcode { + use crate::vm::{ + AsObject, PyObjectRef, PyResult, VirtualMachine, + builtins::{PyBool, PyInt, PyIntRef, PyNone}, + bytecode::Instruction, + match_class, + }; + use std::ops::Deref; + + struct Opcode(Instruction); + + impl Deref for Opcode { + type Target = Instruction; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl Opcode { + #[must_use] + pub fn try_from_pyint(raw: PyIntRef, vm: &VirtualMachine) -> PyResult { + let instruction = raw + .try_to_primitive::(vm) + .and_then(|v| { + Instruction::try_from(v).map_err(|_| { + vm.new_exception_empty(vm.ctx.exceptions.value_error.to_owned()) + }) + }) + .map_err(|_| vm.new_value_error("invalid opcode or oparg"))?; + + Ok(Self(instruction)) + } + } + + #[derive(FromArgs)] + struct StackEffectArgs { + #[pyarg(positional)] + opcode: PyIntRef, + #[pyarg(positional, optional)] + oparg: Option, + #[pyarg(named, optional)] + jump: Option, + } + + #[pyfunction] + fn stack_effect(args: StackEffectArgs, vm: &VirtualMachine) -> PyResult { + let oparg = args + .oparg + .map(|v| { + if !v.fast_isinstance(vm.ctx.types.int_type) { + return Err(vm.new_type_error(format!( + "'{}' object cannot be interpreted as an integer", + v.class().name() + ))); + } + v.downcast_ref::()?.try_to_primitive::(vm) + }) + .unwrap_or(Ok(0))?; + + let jump = args + .jump + .map(|v| { + match_class!(match v { + b @ PyBool => Ok(b.is(&vm.ctx.true_value)), + _n @ PyNone => Ok(false), + _ => { + return Err( + vm.new_value_error("stack_effect: jump must be False, True or None") + ); + } + }) + }) + .unwrap_or(Ok(-1))?; + + let opcode = Opcode::try_from_pyint(args.opcode, vm)?; + + Ok(opcode.stack_effect(oparg.into(), jump)) + } +} From 12aea60cde1f7c56bef78e6fb77319f43f0e7e85 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 17 Sep 2025 20:35:25 +0200 Subject: [PATCH 03/16] Add `test__opcode.py` from 3.13.7 --- Lib/test/test__opcode.py | 139 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 Lib/test/test__opcode.py diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py new file mode 100644 index 0000000000..10f04b64dd --- /dev/null +++ b/Lib/test/test__opcode.py @@ -0,0 +1,139 @@ +import dis +from test.support.import_helper import import_module +import unittest +import opcode + +_opcode = import_module("_opcode") +from _opcode import stack_effect + + +class OpListTests(unittest.TestCase): + def check_bool_function_result(self, func, ops, expected): + for op in ops: + if isinstance(op, str): + op = dis.opmap[op] + with self.subTest(opcode=op, func=func): + self.assertIsInstance(func(op), bool) + self.assertEqual(func(op), expected) + + def test_invalid_opcodes(self): + invalid = [-100, -1, 255, 512, 513, 1000] + self.check_bool_function_result(_opcode.is_valid, invalid, False) + self.check_bool_function_result(_opcode.has_arg, invalid, False) + self.check_bool_function_result(_opcode.has_const, invalid, False) + self.check_bool_function_result(_opcode.has_name, invalid, False) + self.check_bool_function_result(_opcode.has_jump, invalid, False) + self.check_bool_function_result(_opcode.has_free, invalid, False) + self.check_bool_function_result(_opcode.has_local, invalid, False) + self.check_bool_function_result(_opcode.has_exc, invalid, False) + + def test_is_valid(self): + names = [ + 'CACHE', + 'POP_TOP', + 'IMPORT_NAME', + 'JUMP', + 'INSTRUMENTED_RETURN_VALUE', + ] + opcodes = [dis.opmap[opname] for opname in names] + self.check_bool_function_result(_opcode.is_valid, opcodes, True) + + def test_oplists(self): + def check_function(self, func, expected): + for op in [-10, 520]: + with self.subTest(opcode=op, func=func): + res = func(op) + self.assertIsInstance(res, bool) + self.assertEqual(res, op in expected) + + check_function(self, _opcode.has_arg, dis.hasarg) + check_function(self, _opcode.has_const, dis.hasconst) + check_function(self, _opcode.has_name, dis.hasname) + check_function(self, _opcode.has_jump, dis.hasjump) + check_function(self, _opcode.has_free, dis.hasfree) + check_function(self, _opcode.has_local, dis.haslocal) + check_function(self, _opcode.has_exc, dis.hasexc) + + +class StackEffectTests(unittest.TestCase): + def test_stack_effect(self): + self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1) + self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1) + self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 1), -1) + self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 3), -2) + self.assertRaises(ValueError, stack_effect, 30000) + # All defined opcodes + has_arg = dis.hasarg + for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()): + if code >= opcode.MIN_INSTRUMENTED_OPCODE: + continue + with self.subTest(opname=name): + stack_effect(code) + stack_effect(code, 0) + # All not defined opcodes + for code in set(range(256)) - set(dis.opmap.values()): + with self.subTest(opcode=code): + self.assertRaises(ValueError, stack_effect, code) + self.assertRaises(ValueError, stack_effect, code, 0) + + def test_stack_effect_jump(self): + FOR_ITER = dis.opmap['FOR_ITER'] + self.assertEqual(stack_effect(FOR_ITER, 0), 1) + self.assertEqual(stack_effect(FOR_ITER, 0, jump=True), 1) + self.assertEqual(stack_effect(FOR_ITER, 0, jump=False), 1) + JUMP_FORWARD = dis.opmap['JUMP_FORWARD'] + self.assertEqual(stack_effect(JUMP_FORWARD, 0), 0) + self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=True), 0) + self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0) + # All defined opcodes + has_arg = dis.hasarg + has_exc = dis.hasexc + has_jump = dis.hasjabs + dis.hasjrel + for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()): + if code >= opcode.MIN_INSTRUMENTED_OPCODE: + continue + with self.subTest(opname=name): + if code not in has_arg: + common = stack_effect(code) + jump = stack_effect(code, jump=True) + nojump = stack_effect(code, jump=False) + else: + common = stack_effect(code, 0) + jump = stack_effect(code, 0, jump=True) + nojump = stack_effect(code, 0, jump=False) + if code in has_jump or code in has_exc: + self.assertEqual(common, max(jump, nojump)) + else: + self.assertEqual(jump, common) + self.assertEqual(nojump, common) + + +class SpecializationStatsTests(unittest.TestCase): + def test_specialization_stats(self): + stat_names = ["success", "failure", "hit", "deferred", "miss", "deopt"] + specialized_opcodes = [ + op.lower() + for op in opcode._specializations + if opcode._inline_cache_entries.get(op, 0) + ] + self.assertIn('load_attr', specialized_opcodes) + self.assertIn('binary_subscr', specialized_opcodes) + + stats = _opcode.get_specialization_stats() + if stats is not None: + self.assertIsInstance(stats, dict) + self.assertCountEqual(stats.keys(), specialized_opcodes) + self.assertCountEqual( + stats['load_attr'].keys(), + stat_names + ['failure_kinds']) + for sn in stat_names: + self.assertIsInstance(stats['load_attr'][sn], int) + self.assertIsInstance( + stats['load_attr']['failure_kinds'], + tuple) + for v in stats['load_attr']['failure_kinds']: + self.assertIsInstance(v, int) + + +if __name__ == "__main__": + unittest.main() From 16b4eda493b2f5e4c9010200f0976835114bb55b Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:13:46 +0200 Subject: [PATCH 04/16] Impl `has_*` methods --- stdlib/src/opcode.rs | 112 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/stdlib/src/opcode.rs b/stdlib/src/opcode.rs index 2bb6cb429a..d60eba4c8e 100644 --- a/stdlib/src/opcode.rs +++ b/stdlib/src/opcode.rs @@ -21,6 +21,9 @@ mod opcode { } impl Opcode { + // https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/opcode_ids.h#L238 + const HAVE_ARGUMENT: i32 = 44; + #[must_use] pub fn try_from_pyint(raw: PyIntRef, vm: &VirtualMachine) -> PyResult { let instruction = raw @@ -34,6 +37,74 @@ mod opcode { Ok(Self(instruction)) } + + /// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/internal/pycore_opcode_metadata.h#L914-L916 + #[must_use] + const fn is_valid(opcode: i32) -> bool { + opcode >= 0 && opcode < 268 + } + + // All `has_*` methods below mimics + // https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/internal/pycore_opcode_metadata.h#L966-L1190 + + #[must_use] + pub const fn has_arg(opcode: i32) -> bool { + Self::is_valid(opcode) && opcode > Self::HAVE_ARGUMENT + } + + #[must_use] + pub const fn has_const(opcode: i32) -> bool { + Self::is_valid(opcode) && matches!(opcode, 83 | 103 | 240) + } + + #[must_use] + pub const fn has_name(opcode: i32) -> bool { + Self::is_valid(opcode) + && matches!( + opcode, + 63 | 66 + | 67 + | 74 + | 75 + | 82 + | 90 + | 91 + | 92 + | 93 + | 108 + | 113 + | 114 + | 259 + | 260 + | 261 + | 262 + ) + } + + #[must_use] + pub const fn has_jump(opcode: i32) -> bool { + Self::is_valid(opcode) + && matches!( + opcode, + 72 | 77 | 78 | 79 | 97 | 98 | 99 | 100 | 104 | 256 | 257 + ) + } + + #[must_use] + pub const fn has_free(opcode: i32) -> bool { + Self::is_valid(opcode) && matches!(opcode, 64 | 84 | 89 | 94 | 109) + } + + #[must_use] + pub const fn has_local(opcode: i32) -> bool { + Self::is_valid(opcode) + && matches!(opcode, 65 | 85 | 86 | 87 | 88 | 110 | 111 | 112 | 258 | 267) + } + + #[must_use] + pub const fn has_exc(opcode: i32) -> bool { + Self::is_valid(opcode) && matches!(opcode, 264 | 265 | 266) + } } #[derive(FromArgs)] @@ -57,7 +128,9 @@ mod opcode { v.class().name() ))); } - v.downcast_ref::()?.try_to_primitive::(vm) + v.downcast_ref::() + .ok_or_else(|| return vm.new_type_error(""))? + .try_to_primitive::(vm) }) .unwrap_or(Ok(0))?; @@ -74,10 +147,45 @@ mod opcode { } }) }) - .unwrap_or(Ok(-1))?; + .unwrap_or(Ok(false))?; let opcode = Opcode::try_from_pyint(args.opcode, vm)?; Ok(opcode.stack_effect(oparg.into(), jump)) } + + #[pyfunction] + fn has_arg(opcode: i32) -> bool { + Opcode::has_arg(opcode) + } + + #[pyfunction] + fn has_const(opcode: i32) -> bool { + Opcode::has_const(opcode) + } + + #[pyfunction] + fn has_name(opcode: i32) -> bool { + Opcode::has_name(opcode) + } + + #[pyfunction] + fn has_jump(opcode: i32) -> bool { + Opcode::has_jump(opcode) + } + + #[pyfunction] + fn has_free(opcode: i32) -> bool { + Opcode::has_free(opcode) + } + + #[pyfunction] + fn has_local(opcode: i32) -> bool { + Opcode::has_local(opcode) + } + + #[pyfunction] + fn has_exc(opcode: i32) -> bool { + Opcode::has_exc(opcode) + } } From 7fe1032bb5e4ec5aaf6ad39312361c021036c661 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 00:48:25 +0200 Subject: [PATCH 05/16] Add more methods --- stdlib/src/opcode.rs | 96 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/stdlib/src/opcode.rs b/stdlib/src/opcode.rs index d60eba4c8e..76c28d1074 100644 --- a/stdlib/src/opcode.rs +++ b/stdlib/src/opcode.rs @@ -40,7 +40,7 @@ mod opcode { /// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/internal/pycore_opcode_metadata.h#L914-L916 #[must_use] - const fn is_valid(opcode: i32) -> bool { + pub const fn is_valid(opcode: i32) -> bool { opcode >= 0 && opcode < 268 } @@ -107,6 +107,9 @@ mod opcode { } } + #[pyattr] + const ENABLE_SPECIALIZATION: i8 = 1; + #[derive(FromArgs)] struct StackEffectArgs { #[pyarg(positional)] @@ -154,6 +157,11 @@ mod opcode { Ok(opcode.stack_effect(oparg.into(), jump)) } + #[pyfunction] + fn is_valid(opcode: i32) -> bool { + Opcode::is_valid(opcode) + } + #[pyfunction] fn has_arg(opcode: i32) -> bool { Opcode::has_arg(opcode) @@ -188,4 +196,90 @@ mod opcode { fn has_exc(opcode: i32) -> bool { Opcode::has_exc(opcode) } + + #[pyfunction] + fn get_intrinsic1_descs(vm: &VirtualMachine) -> Vec { + [ + "INTRINSIC_1_INVALID", + "INTRINSIC_PRINT", + "INTRINSIC_IMPORT_STAR", + "INTRINSIC_STOPITERATION_ERROR", + "INTRINSIC_ASYNC_GEN_WRAP", + "INTRINSIC_UNARY_POSITIVE", + "INTRINSIC_LIST_TO_TUPLE", + "INTRINSIC_TYPEVAR", + "INTRINSIC_PARAMSPEC", + "INTRINSIC_TYPEVARTUPLE", + "INTRINSIC_SUBSCRIPT_GENERIC", + "INTRINSIC_TYPEALIAS", + ] + .into_iter() + .map(|x| vm.ctx.new_str(x).into()) + .collect() + } + + #[pyfunction] + fn get_intrinsic2_descs(vm: &VirtualMachine) -> Vec { + [ + "INTRINSIC_2_INVALID", + "INTRINSIC_PREP_RERAISE_STAR", + "INTRINSIC_TYPEVAR_WITH_BOUND", + "INTRINSIC_TYPEVAR_WITH_CONSTRAINTS", + "INTRINSIC_SET_FUNCTION_TYPE_PARAMS", + "INTRINSIC_SET_TYPEPARAM_DEFAULT", + ] + .into_iter() + .map(|x| vm.ctx.new_str(x).into()) + .collect() + } + + #[pyfunction] + fn get_nb_ops(vm: &VirtualMachine) -> Vec { + [ + ("NB_ADD", "+"), + ("NB_AND", "&"), + ("NB_FLOOR_DIVIDE", "//"), + ("NB_LSHIFT", "<<"), + ("NB_MATRIX_MULTIPLY", "@"), + ("NB_MULTIPLY", "*"), + ("NB_REMAINDER", "%"), + ("NB_OR", "|"), + ("NB_POWER", "**"), + ("NB_RSHIFT", ">>"), + ("NB_SUBTRACT", "-"), + ("NB_TRUE_DIVIDE", "/"), + ("NB_XOR", "^"), + ("NB_INPLACE_ADD", "+="), + ("NB_INPLACE_AND", "&="), + ("NB_INPLACE_FLOOR_DIVIDE", "//="), + ("NB_INPLACE_LSHIFT", "<<="), + ("NB_INPLACE_MATRIX_MULTIPLY", "@="), + ("NB_INPLACE_MULTIPLY", "*="), + ("NB_INPLACE_REMAINDER", "%="), + ("NB_INPLACE_OR", "|="), + ("NB_INPLACE_POWER", "**="), + ("NB_INPLACE_RSHIFT", ">>="), + ("NB_INPLACE_SUBTRACT", "-="), + ("NB_INPLACE_TRUE_DIVIDE", "/="), + ("NB_INPLACE_XOR", "^="), + ] + .into_iter() + .map(|(a, b)| { + vm.ctx + .new_tuple(vec![vm.ctx.new_str(a).into(), vm.ctx.new_str(b).into()]) + .into() + }) + .collect() + } + + #[pyfunction] + fn get_executor(_code: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // TODO + Ok(vm.ctx.none()) + } + + #[pyfunction] + fn get_specialization_stats(vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.none() + } } From b5b75342e304e7890faddb80dae4511845b4b83f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:10:15 +0200 Subject: [PATCH 06/16] Update `dis.py` from 3.13.7 --- Lib/dis.py | 1079 ++++++++++++++++++- Lib/test/test_dis.py | 2407 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 3429 insertions(+), 57 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 53c85555bc..797e0f8a08 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -1,18 +1,1075 @@ -from _dis import * +"""Disassembler of Python byte code into mnemonics.""" +import sys +import types +import collections +import io -# Disassembling a file by following cpython Lib/dis.py -def _test(): - """Simple test program to disassemble a file.""" +from opcode import * +from opcode import ( + __all__ as _opcodes_all, + _cache_format, + _inline_cache_entries, + _nb_ops, + _intrinsic_1_descs, + _intrinsic_2_descs, + _specializations, + _specialized_opmap, +) + +from _opcode import get_executor + +__all__ = ["code_info", "dis", "disassemble", "distb", "disco", + "findlinestarts", "findlabels", "show_code", + "get_instructions", "Instruction", "Bytecode"] + _opcodes_all +del _opcodes_all + +_have_code = (types.MethodType, types.FunctionType, types.CodeType, + classmethod, staticmethod, type) + +CONVERT_VALUE = opmap['CONVERT_VALUE'] + +SET_FUNCTION_ATTRIBUTE = opmap['SET_FUNCTION_ATTRIBUTE'] +FUNCTION_ATTR_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure') + +ENTER_EXECUTOR = opmap['ENTER_EXECUTOR'] +LOAD_CONST = opmap['LOAD_CONST'] +RETURN_CONST = opmap['RETURN_CONST'] +LOAD_GLOBAL = opmap['LOAD_GLOBAL'] +BINARY_OP = opmap['BINARY_OP'] +JUMP_BACKWARD = opmap['JUMP_BACKWARD'] +FOR_ITER = opmap['FOR_ITER'] +SEND = opmap['SEND'] +LOAD_ATTR = opmap['LOAD_ATTR'] +LOAD_SUPER_ATTR = opmap['LOAD_SUPER_ATTR'] +CALL_INTRINSIC_1 = opmap['CALL_INTRINSIC_1'] +CALL_INTRINSIC_2 = opmap['CALL_INTRINSIC_2'] +LOAD_FAST_LOAD_FAST = opmap['LOAD_FAST_LOAD_FAST'] +STORE_FAST_LOAD_FAST = opmap['STORE_FAST_LOAD_FAST'] +STORE_FAST_STORE_FAST = opmap['STORE_FAST_STORE_FAST'] + +CACHE = opmap["CACHE"] + +_all_opname = list(opname) +_all_opmap = dict(opmap) +for name, op in _specialized_opmap.items(): + # fill opname and opmap + assert op < len(_all_opname) + _all_opname[op] = name + _all_opmap[name] = op + +deoptmap = { + specialized: base for base, family in _specializations.items() for specialized in family +} + +def _try_compile(source, name): + """Attempts to compile the given source, first as an expression and + then as a statement if the first approach fails. + + Utility function to accept strings in functions that otherwise + expect code objects + """ + try: + return compile(source, name, 'eval') + except SyntaxError: + pass + return compile(source, name, 'exec') + +def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, + show_offsets=False): + """Disassemble classes, methods, functions, and other compiled objects. + + With no argument, disassemble the last traceback. + + Compiled objects currently include generator objects, async generator + objects, and coroutine objects, all of which store their code object + in a special attribute. + """ + if x is None: + distb(file=file, show_caches=show_caches, adaptive=adaptive, + show_offsets=show_offsets) + return + # Extract functions from methods. + if hasattr(x, '__func__'): + x = x.__func__ + # Extract compiled code objects from... + if hasattr(x, '__code__'): # ...a function, or + x = x.__code__ + elif hasattr(x, 'gi_code'): #...a generator object, or + x = x.gi_code + elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or + x = x.ag_code + elif hasattr(x, 'cr_code'): #...a coroutine. + x = x.cr_code + # Perform the disassembly. + if hasattr(x, '__dict__'): # Class or module + items = sorted(x.__dict__.items()) + for name, x1 in items: + if isinstance(x1, _have_code): + print("Disassembly of %s:" % name, file=file) + try: + dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + except TypeError as msg: + print("Sorry:", msg, file=file) + print(file=file) + elif hasattr(x, 'co_code'): # Code object + _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + elif isinstance(x, (bytes, bytearray)): # Raw bytecode + labels_map = _make_labels_map(x) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=file, + offset_width=len(str(max(len(x) - 2, 9999))) if show_offsets else 0, + label_width=label_width, + show_caches=show_caches) + arg_resolver = ArgResolver(labels_map=labels_map) + _disassemble_bytes(x, arg_resolver=arg_resolver, formatter=formatter) + elif isinstance(x, str): # Source code + _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + else: + raise TypeError("don't know how to disassemble %s objects" % + type(x).__name__) + +def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets=False): + """Disassemble a traceback (default: last traceback).""" + if tb is None: + try: + if hasattr(sys, 'last_exc'): + tb = sys.last_exc.__traceback__ + else: + tb = sys.last_traceback + except AttributeError: + raise RuntimeError("no last traceback to disassemble") from None + while tb.tb_next: tb = tb.tb_next + disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + +# The inspect module interrogates this dictionary to build its +# list of CO_* constants. It is also used by pretty_flags to +# turn the co_flags field into a human readable list. +COMPILER_FLAG_NAMES = { + 1: "OPTIMIZED", + 2: "NEWLOCALS", + 4: "VARARGS", + 8: "VARKEYWORDS", + 16: "NESTED", + 32: "GENERATOR", + 64: "NOFREE", + 128: "COROUTINE", + 256: "ITERABLE_COROUTINE", + 512: "ASYNC_GENERATOR", +} + +def pretty_flags(flags): + """Return pretty representation of code flags.""" + names = [] + for i in range(32): + flag = 1<" + +# Sentinel to represent values that cannot be calculated +UNKNOWN = _Unknown() + +def _get_code_object(x): + """Helper to handle methods, compiled or raw code objects, and strings.""" + # Extract functions from methods. + if hasattr(x, '__func__'): + x = x.__func__ + # Extract compiled code objects from... + if hasattr(x, '__code__'): # ...a function, or + x = x.__code__ + elif hasattr(x, 'gi_code'): #...a generator object, or + x = x.gi_code + elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or + x = x.ag_code + elif hasattr(x, 'cr_code'): #...a coroutine. + x = x.cr_code + # Handle source code. + if isinstance(x, str): + x = _try_compile(x, "") + # By now, if we don't have a code object, we can't disassemble x. + if hasattr(x, 'co_code'): + return x + raise TypeError("don't know how to disassemble %s objects" % + type(x).__name__) + +def _deoptop(op): + name = _all_opname[op] + return _all_opmap[deoptmap[name]] if name in deoptmap else op + +def _get_code_array(co, adaptive): + if adaptive: + code = co._co_code_adaptive + res = [] + found = False + for i in range(0, len(code), 2): + op, arg = code[i], code[i+1] + if op == ENTER_EXECUTOR: + try: + ex = get_executor(co, i) + except (ValueError, RuntimeError): + ex = None + + if ex: + op, arg = ex.get_opcode(), ex.get_oparg() + found = True + + res.append(op.to_bytes()) + res.append(arg.to_bytes()) + return code if not found else b''.join(res) + else: + return co.co_code + +def code_info(x): + """Formatted details of methods, functions, or code.""" + return _format_code_info(_get_code_object(x)) + +def _format_code_info(co): + lines = [] + lines.append("Name: %s" % co.co_name) + lines.append("Filename: %s" % co.co_filename) + lines.append("Argument count: %s" % co.co_argcount) + lines.append("Positional-only arguments: %s" % co.co_posonlyargcount) + lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount) + lines.append("Number of locals: %s" % co.co_nlocals) + lines.append("Stack size: %s" % co.co_stacksize) + lines.append("Flags: %s" % pretty_flags(co.co_flags)) + if co.co_consts: + lines.append("Constants:") + for i_c in enumerate(co.co_consts): + lines.append("%4d: %r" % i_c) + if co.co_names: + lines.append("Names:") + for i_n in enumerate(co.co_names): + lines.append("%4d: %s" % i_n) + if co.co_varnames: + lines.append("Variable names:") + for i_n in enumerate(co.co_varnames): + lines.append("%4d: %s" % i_n) + if co.co_freevars: + lines.append("Free variables:") + for i_n in enumerate(co.co_freevars): + lines.append("%4d: %s" % i_n) + if co.co_cellvars: + lines.append("Cell variables:") + for i_n in enumerate(co.co_cellvars): + lines.append("%4d: %s" % i_n) + return "\n".join(lines) + +def show_code(co, *, file=None): + """Print details of methods, functions, or code to *file*. + + If *file* is not provided, the output is printed on stdout. + """ + print(code_info(co), file=file) + +Positions = collections.namedtuple( + 'Positions', + [ + 'lineno', + 'end_lineno', + 'col_offset', + 'end_col_offset', + ], + defaults=[None] * 4 +) + +_Instruction = collections.namedtuple( + "_Instruction", + [ + 'opname', + 'opcode', + 'arg', + 'argval', + 'argrepr', + 'offset', + 'start_offset', + 'starts_line', + 'line_number', + 'label', + 'positions', + 'cache_info', + ], + defaults=[None, None, None] +) + +_Instruction.opname.__doc__ = "Human readable name for operation" +_Instruction.opcode.__doc__ = "Numeric code for operation" +_Instruction.arg.__doc__ = "Numeric argument to operation (if any), otherwise None" +_Instruction.argval.__doc__ = "Resolved arg value (if known), otherwise same as arg" +_Instruction.argrepr.__doc__ = "Human readable description of operation argument" +_Instruction.offset.__doc__ = "Start index of operation within bytecode sequence" +_Instruction.start_offset.__doc__ = ( + "Start index of operation within bytecode sequence, including extended args if present; " + "otherwise equal to Instruction.offset" +) +_Instruction.starts_line.__doc__ = "True if this opcode starts a source line, otherwise False" +_Instruction.line_number.__doc__ = "source line number associated with this opcode (if any), otherwise None" +_Instruction.label.__doc__ = "A label (int > 0) if this instruction is a jump target, otherwise None" +_Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction" +_Instruction.cache_info.__doc__ = "list of (name, size, data), one for each cache entry of the instruction" + +_ExceptionTableEntryBase = collections.namedtuple("_ExceptionTableEntryBase", + "start end target depth lasti") + +class _ExceptionTableEntry(_ExceptionTableEntryBase): + pass + +_OPNAME_WIDTH = 20 +_OPARG_WIDTH = 5 + +def _get_cache_size(opname): + return _inline_cache_entries.get(opname, 0) + +def _get_jump_target(op, arg, offset): + """Gets the bytecode offset of the jump target if this is a jump instruction. + + Otherwise return None. + """ + deop = _deoptop(op) + caches = _get_cache_size(_all_opname[deop]) + if deop in hasjrel: + if _is_backward_jump(deop): + arg = -arg + target = offset + 2 + arg*2 + target += 2 * caches + elif deop in hasjabs: + target = arg*2 + else: + target = None + return target + +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 + start_offset - start index of operation within bytecode sequence including extended args if present; + otherwise equal to Instruction.offset + starts_line - True if this opcode starts a source line, otherwise False + line_number - source line number associated with this opcode (if any), otherwise None + label - A label if this instruction is a jump target, otherwise None + positions - Optional dis.Positions object holding the span of source code + covered by this instruction + cache_info - information about the format and content of the instruction's cache + entries (if any) + """ + + @property + def oparg(self): + """Alias for Instruction.arg.""" + return self.arg + + @property + def baseopcode(self): + """Numeric code for the base operation if operation is specialized. + + Otherwise equal to Instruction.opcode. + """ + return _deoptop(self.opcode) + + @property + def baseopname(self): + """Human readable name for the base operation if operation is specialized. + + Otherwise equal to Instruction.opname. + """ + return opname[self.baseopcode] + + @property + def cache_offset(self): + """Start index of the cache entries following the operation.""" + return self.offset + 2 + + @property + def end_offset(self): + """End index of the cache entries following the operation.""" + return self.cache_offset + _get_cache_size(_all_opname[self.opcode])*2 + + @property + def jump_target(self): + """Bytecode index of the jump target if this is a jump operation. + + Otherwise return None. + """ + return _get_jump_target(self.opcode, self.arg, self.offset) + + @property + def is_jump_target(self): + """True if other code jumps to here, otherwise False""" + return self.label is not None + + def __str__(self): + output = io.StringIO() + formatter = Formatter(file=output) + formatter.print_instruction(self, False) + return output.getvalue() + + +class Formatter: + + def __init__(self, file=None, lineno_width=0, offset_width=0, label_width=0, + line_offset=0, show_caches=False): + """Create a Formatter + + *file* where to write the output + *lineno_width* sets the width of the line number field (0 omits it) + *offset_width* sets the width of the instruction offset field + *label_width* sets the width of the label field + *show_caches* is a boolean indicating whether to display cache lines + + """ + self.file = file + self.lineno_width = lineno_width + self.offset_width = offset_width + self.label_width = label_width + self.show_caches = show_caches + + def print_instruction(self, instr, mark_as_current=False): + self.print_instruction_line(instr, mark_as_current) + if self.show_caches and instr.cache_info: + offset = instr.offset + for name, size, data in instr.cache_info: + for i in range(size): + offset += 2 + # Only show the fancy argrepr for a CACHE instruction when it's + # the first entry for a particular cache value: + if i == 0: + argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" + else: + argrepr = "" + self.print_instruction_line( + Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, + False, None, None, instr.positions), + False) + + def print_instruction_line(self, instr, mark_as_current): + """Format instruction details for inclusion in disassembly output.""" + lineno_width = self.lineno_width + offset_width = self.offset_width + label_width = self.label_width + + new_source_line = (lineno_width > 0 and + instr.starts_line and + instr.offset > 0) + if new_source_line: + print(file=self.file) + + fields = [] + # Column: Source code line number + if lineno_width: + if instr.starts_line: + lineno_fmt = "%%%dd" if instr.line_number is not None else "%%%ds" + lineno_fmt = lineno_fmt % lineno_width + lineno = _NO_LINENO if instr.line_number is None else instr.line_number + fields.append(lineno_fmt % lineno) + else: + fields.append(' ' * lineno_width) + # Column: Label + if instr.label is not None: + lbl = f"L{instr.label}:" + fields.append(f"{lbl:>{label_width}}") + else: + fields.append(' ' * label_width) + # Column: Instruction offset from start of code sequence + if offset_width > 0: + fields.append(f"{repr(instr.offset):>{offset_width}} ") + # Column: Current instruction indicator + if mark_as_current: + fields.append('-->') + else: + fields.append(' ') + # Column: Opcode name + fields.append(instr.opname.ljust(_OPNAME_WIDTH)) + # Column: Opcode argument + if instr.arg is not None: + arg = repr(instr.arg) + # If opname is longer than _OPNAME_WIDTH, we allow it to overflow into + # the space reserved for oparg. This results in fewer misaligned opargs + # in the disassembly output. + opname_excess = max(0, len(instr.opname) - _OPNAME_WIDTH) + fields.append(repr(instr.arg).rjust(_OPARG_WIDTH - opname_excess)) + # Column: Opcode argument details + if instr.argrepr: + fields.append('(' + instr.argrepr + ')') + print(' '.join(fields).rstrip(), file=self.file) + + def print_exception_table(self, exception_entries): + file = self.file + if exception_entries: + print("ExceptionTable:", file=file) + for entry in exception_entries: + lasti = " lasti" if entry.lasti else "" + start = entry.start_label + end = entry.end_label + target = entry.target_label + print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file) + + +class ArgResolver: + def __init__(self, co_consts=None, names=None, varname_from_oparg=None, labels_map=None): + self.co_consts = co_consts + self.names = names + self.varname_from_oparg = varname_from_oparg + self.labels_map = labels_map or {} + + def offset_from_jump_arg(self, op, arg, offset): + deop = _deoptop(op) + if deop in hasjabs: + return arg * 2 + elif deop in hasjrel: + signed_arg = -arg if _is_backward_jump(deop) else arg + argval = offset + 2 + signed_arg*2 + caches = _get_cache_size(_all_opname[deop]) + argval += 2 * caches + return argval + return None + + def get_label_for_offset(self, offset): + return self.labels_map.get(offset, None) + + def get_argval_argrepr(self, op, arg, offset): + get_name = None if self.names is None else self.names.__getitem__ + argval = None + argrepr = '' + deop = _deoptop(op) + if arg is not None: + # Set argval to the dereferenced value of the argument when + # available, and argrepr to the string representation of argval. + # _disassemble_bytes needs the string repr of the + # raw name index for LOAD_GLOBAL, LOAD_CONST, etc. + argval = arg + if deop in hasconst: + argval, argrepr = _get_const_info(deop, arg, self.co_consts) + elif deop in hasname: + if deop == LOAD_GLOBAL: + argval, argrepr = _get_name_info(arg//2, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL" + elif deop == LOAD_ATTR: + argval, argrepr = _get_name_info(arg//2, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL|self" + elif deop == LOAD_SUPER_ATTR: + argval, argrepr = _get_name_info(arg//4, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL|self" + else: + argval, argrepr = _get_name_info(arg, get_name) + elif deop in hasjump or deop in hasexc: + argval = self.offset_from_jump_arg(op, arg, offset) + lbl = self.get_label_for_offset(argval) + assert lbl is not None + argrepr = f"to L{lbl}" + elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST): + arg1 = arg >> 4 + arg2 = arg & 15 + val1, argrepr1 = _get_name_info(arg1, self.varname_from_oparg) + val2, argrepr2 = _get_name_info(arg2, self.varname_from_oparg) + argrepr = argrepr1 + ", " + argrepr2 + argval = val1, val2 + elif deop in haslocal or deop in hasfree: + argval, argrepr = _get_name_info(arg, self.varname_from_oparg) + elif deop in hascompare: + argval = cmp_op[arg >> 5] + argrepr = argval + if arg & 16: + argrepr = f"bool({argrepr})" + elif deop == CONVERT_VALUE: + argval = (None, str, repr, ascii)[arg] + argrepr = ('', 'str', 'repr', 'ascii')[arg] + elif deop == SET_FUNCTION_ATTRIBUTE: + argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS) + if arg & (1<> 1 + lasti = bool(dl&1) + entries.append(_ExceptionTableEntry(start, end, target, depth, lasti)) + except StopIteration: + return entries + +def _is_backward_jump(op): + return opname[op] in ('JUMP_BACKWARD', + 'JUMP_BACKWARD_NO_INTERRUPT') + +def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=None, + original_code=None, arg_resolver=None): + """Iterate over the instructions in a bytecode string. + + Generates a sequence of Instruction namedtuples giving the details of each + opcode. + + """ + # Use the basic, unadaptive code for finding labels and actually walking the + # bytecode, since replacements like ENTER_EXECUTOR and INSTRUMENTED_* can + # mess that logic up pretty badly: + original_code = original_code or code + co_positions = co_positions or iter(()) + + starts_line = False + local_line_number = None + line_number = None + for offset, start_offset, op, arg in _unpack_opargs(original_code): + if linestarts is not None: + starts_line = offset in linestarts + if starts_line: + local_line_number = linestarts[offset] + if local_line_number is not None: + line_number = local_line_number + line_offset + else: + line_number = None + positions = Positions(*next(co_positions, ())) + deop = _deoptop(op) + op = code[offset] + + if arg_resolver: + argval, argrepr = arg_resolver.get_argval_argrepr(op, arg, offset) + else: + argval, argrepr = arg, repr(arg) + + caches = _get_cache_size(_all_opname[deop]) + # Advance the co_positions iterator: + for _ in range(caches): + next(co_positions, ()) + + if caches: + cache_info = [] + for name, size in _cache_format[opname[deop]].items(): + data = code[offset + 2: offset + 2 + 2 * size] + cache_info.append((name, size, data)) + else: + cache_info = None + + label = arg_resolver.get_label_for_offset(offset) if arg_resolver else None + yield Instruction(_all_opname[op], op, arg, argval, argrepr, + offset, start_offset, starts_line, line_number, + label, positions, cache_info) + + +def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, + show_offsets=False): + """Disassemble a code object.""" + linestarts = dict(findlinestarts(co)) + exception_entries = _parse_exception_table(co) + labels_map = _make_labels_map(co.co_code, exception_entries=exception_entries) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=file, + lineno_width=_get_lineno_width(linestarts), + offset_width=len(str(max(len(co.co_code) - 2, 9999))) if show_offsets else 0, + label_width=label_width, + show_caches=show_caches) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + _disassemble_bytes(_get_code_array(co, adaptive), lasti, linestarts, + exception_entries=exception_entries, co_positions=co.co_positions(), + original_code=co.co_code, arg_resolver=arg_resolver, formatter=formatter) + +def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False): + disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) + if depth is None or depth > 0: + if depth is not None: + depth = depth - 1 + for x in co.co_consts: + if hasattr(x, 'co_code'): + print(file=file) + print("Disassembly of %r:" % (x,), file=file) + _disassemble_recursive( + x, file=file, depth=depth, show_caches=show_caches, + adaptive=adaptive, show_offsets=show_offsets + ) + + +def _make_labels_map(original_code, exception_entries=()): + jump_targets = set(findlabels(original_code)) + labels = set(jump_targets) + for start, end, target, _, _ in exception_entries: + labels.add(start) + labels.add(end) + labels.add(target) + labels = sorted(labels) + labels_map = {offset: i+1 for (i, offset) in enumerate(sorted(labels))} + for e in exception_entries: + e.start_label = labels_map[e.start] + e.end_label = labels_map[e.end] + e.target_label = labels_map[e.target] + return labels_map + +_NO_LINENO = ' --' + +def _get_lineno_width(linestarts): + if linestarts is None: + return 0 + maxlineno = max(filter(None, linestarts.values()), default=-1) + if maxlineno == -1: + # Omit the line number column entirely if we have no line number info + return 0 + lineno_width = max(3, len(str(maxlineno))) + if lineno_width < len(_NO_LINENO) and None in linestarts.values(): + lineno_width = len(_NO_LINENO) + return lineno_width + + +def _disassemble_bytes(code, lasti=-1, linestarts=None, + *, line_offset=0, exception_entries=(), + co_positions=None, original_code=None, + arg_resolver=None, formatter=None): + + assert formatter is not None + assert arg_resolver is not None + + instrs = _get_instructions_bytes(code, linestarts=linestarts, + line_offset=line_offset, + co_positions=co_positions, + original_code=original_code, + arg_resolver=arg_resolver) + + print_instructions(instrs, exception_entries, formatter, lasti=lasti) + + +def print_instructions(instrs, exception_entries, formatter, lasti=-1): + for instr in instrs: + # Each CACHE takes 2 bytes + is_current_instr = instr.offset <= lasti \ + <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) + formatter.print_instruction(instr, is_current_instr) + + formatter.print_exception_table(exception_entries) + +def _disassemble_str(source, **kwargs): + """Compile the source string, then disassemble the code object.""" + _disassemble_recursive(_try_compile(source, ''), **kwargs) + +disco = disassemble # XXX For backwards compatibility + + +# Rely on C `int` being 32 bits for oparg +_INT_BITS = 32 +# Value for c int when it overflows +_INT_OVERFLOW = 2 ** (_INT_BITS - 1) + +def _unpack_opargs(code): + extended_arg = 0 + extended_args_offset = 0 # Number of EXTENDED_ARG instructions preceding the current instruction + caches = 0 + for i in range(0, len(code), 2): + # Skip inline CACHE entries: + if caches: + caches -= 1 + continue + op = code[i] + deop = _deoptop(op) + caches = _get_cache_size(_all_opname[deop]) + if deop in hasarg: + arg = code[i+1] | extended_arg + extended_arg = (arg << 8) if deop == EXTENDED_ARG else 0 + # The oparg is stored as a signed integer + # If the value exceeds its upper limit, it will overflow and wrap + # to a negative integer + if extended_arg >= _INT_OVERFLOW: + extended_arg -= 2 * _INT_OVERFLOW + else: + arg = None + extended_arg = 0 + if deop == EXTENDED_ARG: + extended_args_offset += 1 + yield (i, i, op, arg) + else: + start_offset = i - extended_args_offset*2 + yield (i, start_offset, op, arg) + extended_args_offset = 0 + +def findlabels(code): + """Detect all offsets in a byte code which are jump targets. + + Return the list of offsets. + + """ + labels = [] + for offset, _, op, arg in _unpack_opargs(code): + if arg is not None: + label = _get_jump_target(op, arg, offset) + if label is None: + continue + if label not in labels: + labels.append(label) + return labels + +def findlinestarts(code): + """Find the offsets in a byte code which are start of lines in the source. + + Generate pairs (offset, lineno) + lineno will be an integer or None the offset does not have a source line. + """ + + lastline = False # None is a valid line number + for start, end, line in code.co_lines(): + if line is not lastline: + lastline = line + yield start, line + return + +def _find_imports(co): + """Find import statements in the code + + Generate triplets (name, level, fromlist) where + name is the imported module and level, fromlist are + the corresponding args to __import__. + """ + IMPORT_NAME = opmap['IMPORT_NAME'] + + consts = co.co_consts + names = co.co_names + opargs = [(op, arg) for _, _, op, arg in _unpack_opargs(co.co_code) + if op != EXTENDED_ARG] + for i, (op, oparg) in enumerate(opargs): + if op == IMPORT_NAME and i >= 2: + from_op = opargs[i-1] + level_op = opargs[i-2] + if (from_op[0] in hasconst and level_op[0] in hasconst): + level = _get_const_value(level_op[0], level_op[1], consts) + fromlist = _get_const_value(from_op[0], from_op[1], consts) + yield (names[oparg], level, fromlist) + +def _find_store_names(co): + """Find names of variables which are written in the code + + Generate sequence of strings + """ + STORE_OPS = { + opmap['STORE_NAME'], + opmap['STORE_GLOBAL'] + } + + names = co.co_names + for _, _, op, arg in _unpack_opargs(co.co_code): + if op in STORE_OPS: + yield names[arg] + + +class Bytecode: + """The bytecode operations of a piece of code + + Instantiate this with a function, method, other compiled object, 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, show_caches=False, adaptive=False, show_offsets=False): + self.codeobj = co = _get_code_object(x) + if first_line is None: + self.first_line = co.co_firstlineno + self._line_offset = 0 + else: + self.first_line = first_line + self._line_offset = first_line - co.co_firstlineno + self._linestarts = dict(findlinestarts(co)) + self._original_object = x + self.current_offset = current_offset + self.exception_entries = _parse_exception_table(co) + self.show_caches = show_caches + self.adaptive = adaptive + self.show_offsets = show_offsets + + def __iter__(self): + co = self.codeobj + original_code = co.co_code + labels_map = _make_labels_map(original_code, self.exception_entries) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + return _get_instructions_bytes(_get_code_array(co, self.adaptive), + linestarts=self._linestarts, + line_offset=self._line_offset, + co_positions=co.co_positions(), + original_code=original_code, + arg_resolver=arg_resolver) + + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, + self._original_object) + + @classmethod + def from_traceback(cls, tb, *, show_caches=False, adaptive=False): + """ Construct a Bytecode from the given traceback """ + while tb.tb_next: + tb = tb.tb_next + return cls( + tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, adaptive=adaptive + ) + + def info(self): + """Return formatted information about the code object.""" + return _format_code_info(self.codeobj) + + def dis(self): + """Return a formatted view of the bytecode operations.""" + co = self.codeobj + if self.current_offset is not None: + offset = self.current_offset + else: + offset = -1 + with io.StringIO() as output: + code = _get_code_array(co, self.adaptive) + offset_width = len(str(max(len(code) - 2, 9999))) if self.show_offsets else 0 + + + labels_map = _make_labels_map(co.co_code, self.exception_entries) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=output, + lineno_width=_get_lineno_width(self._linestarts), + offset_width=offset_width, + label_width=label_width, + line_offset=self._line_offset, + show_caches=self.show_caches) + + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + _disassemble_bytes(code, + linestarts=self._linestarts, + line_offset=self._line_offset, + lasti=offset, + exception_entries=self.exception_entries, + co_positions=co.co_positions(), + original_code=co.co_code, + arg_resolver=arg_resolver, + formatter=formatter) + return output.getvalue() + + +def main(args=None): import argparse parser = argparse.ArgumentParser() - parser.add_argument('infile', type=argparse.FileType('rb'), nargs='?', default='-') - args = parser.parse_args() - with args.infile as infile: - source = infile.read() - code = compile(source, args.infile.name, "exec") - dis(code) + parser.add_argument('-C', '--show-caches', action='store_true', + help='show inline caches') + parser.add_argument('-O', '--show-offsets', action='store_true', + help='show instruction offsets') + parser.add_argument('infile', nargs='?', default='-') + args = parser.parse_args(args=args) + if args.infile == '-': + name = '' + source = sys.stdin.buffer.read() + else: + name = args.infile + with open(args.infile, 'rb') as infile: + source = infile.read() + code = compile(source, name, "exec") + dis(code, show_caches=args.show_caches, show_offsets=args.show_offsets) if __name__ == "__main__": - _test() + main() diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 8bbba86a46..73807d7fa9 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1,59 +1,2374 @@ -import subprocess +# Minimal tests for dis module + +import contextlib +import dis +import functools +import io +import itertools +import opcode +import re import sys +import tempfile +import textwrap +import types import unittest +from test.support import (captured_stdout, requires_debug_ranges, + requires_specialization, cpython_only, + os_helper) +from test.support.bytecode_helper import BytecodeTestCase + + +CACHE = dis.opmap["CACHE"] + +def get_tb(): + def _error(): + try: + 1 / 0 + except Exception as e: + tb = e.__traceback__ + return tb + + tb = _error() + while tb.tb_next: + tb = tb.tb_next + return tb -# This only tests that it prints something in order -# to avoid changing this test if the bytecode changes +TRACEBACK_CODE = get_tb().tb_frame.f_code -# These tests start a new process instead of redirecting stdout because -# stdout is being written to by rust code, which currently can't be -# redirected by reassigning sys.stdout +class _C: + def __init__(self, x): + self.x = x == 1 + @staticmethod + def sm(x): + x = x == 1 -class TestDis(unittest.TestCase): @classmethod - def setUpClass(cls): - cls.setup = """ -import dis -def tested_func(): pass + def cm(cls, x): + cls.x = x == 1 + +dis_c_instance_method = """\ +%3d RESUME 0 + +%3d LOAD_FAST 1 (x) + LOAD_CONST 1 (1) + COMPARE_OP 72 (==) + LOAD_FAST 0 (self) + STORE_ATTR 0 (x) + RETURN_CONST 0 (None) +""" % (_C.__init__.__code__.co_firstlineno, _C.__init__.__code__.co_firstlineno + 1,) + +dis_c_instance_method_bytes = """\ + RESUME 0 + LOAD_FAST 1 + LOAD_CONST 1 + COMPARE_OP 72 (==) + LOAD_FAST 0 + STORE_ATTR 0 + RETURN_CONST 0 +""" + +dis_c_class_method = """\ +%3d RESUME 0 + +%3d LOAD_FAST 1 (x) + LOAD_CONST 1 (1) + COMPARE_OP 72 (==) + LOAD_FAST 0 (cls) + STORE_ATTR 0 (x) + RETURN_CONST 0 (None) +""" % (_C.cm.__code__.co_firstlineno, _C.cm.__code__.co_firstlineno + 2,) + +dis_c_static_method = """\ +%3d RESUME 0 + +%3d LOAD_FAST 0 (x) + LOAD_CONST 1 (1) + COMPARE_OP 72 (==) + STORE_FAST 0 (x) + RETURN_CONST 0 (None) +""" % (_C.sm.__code__.co_firstlineno, _C.sm.__code__.co_firstlineno + 2,) + +# Class disassembling info has an extra newline at end. +dis_c = """\ +Disassembly of %s: +%s +Disassembly of %s: +%s +Disassembly of %s: +%s +""" % (_C.__init__.__name__, dis_c_instance_method, + _C.cm.__name__, dis_c_class_method, + _C.sm.__name__, dis_c_static_method) + +def _f(a): + print(a) + return 1 + +dis_f = """\ +%3d RESUME 0 + +%3d LOAD_GLOBAL 1 (print + NULL) + LOAD_FAST 0 (a) + CALL 1 + POP_TOP + +%3d RETURN_CONST 1 (1) +""" % (_f.__code__.co_firstlineno, + _f.__code__.co_firstlineno + 1, + _f.__code__.co_firstlineno + 2) + +dis_f_with_offsets = """\ +%3d 0 RESUME 0 + +%3d 2 LOAD_GLOBAL 1 (print + NULL) + 12 LOAD_FAST 0 (a) + 14 CALL 1 + 22 POP_TOP + +%3d 24 RETURN_CONST 1 (1) +""" % (_f.__code__.co_firstlineno, + _f.__code__.co_firstlineno + 1, + _f.__code__.co_firstlineno + 2) + + +dis_f_co_code = """\ + RESUME 0 + LOAD_GLOBAL 1 + LOAD_FAST 0 + CALL 1 + POP_TOP + RETURN_CONST 1 +""" + +def bug708901(): + for res in range(1, + 10): + pass + +dis_bug708901 = """\ +%3d RESUME 0 + +%3d LOAD_GLOBAL 1 (range + NULL) + LOAD_CONST 1 (1) + +%3d LOAD_CONST 2 (10) + +%3d CALL 2 + GET_ITER + L1: FOR_ITER 3 (to L2) + STORE_FAST 0 (res) + +%3d JUMP_BACKWARD 5 (to L1) + +%3d L2: END_FOR + POP_TOP + RETURN_CONST 0 (None) +""" % (bug708901.__code__.co_firstlineno, + bug708901.__code__.co_firstlineno + 1, + bug708901.__code__.co_firstlineno + 2, + bug708901.__code__.co_firstlineno + 1, + bug708901.__code__.co_firstlineno + 3, + bug708901.__code__.co_firstlineno + 1) + + +def bug1333982(x=[]): + assert 0, ((s for s in x) + + 1) + pass + +dis_bug1333982 = """\ +%3d RESUME 0 + +%3d LOAD_ASSERTION_ERROR + LOAD_CONST 1 ( at 0x..., file "%s", line %d>) + MAKE_FUNCTION + LOAD_FAST 0 (x) + GET_ITER + CALL 0 + +%3d LOAD_CONST 2 (1) + +%3d BINARY_OP 0 (+) + CALL 0 + RAISE_VARARGS 1 +""" % (bug1333982.__code__.co_firstlineno, + bug1333982.__code__.co_firstlineno + 1, + __file__, + bug1333982.__code__.co_firstlineno + 1, + bug1333982.__code__.co_firstlineno + 2, + bug1333982.__code__.co_firstlineno + 1) + + +def bug42562(): + pass + + +# Set line number for 'pass' to None +bug42562.__code__ = bug42562.__code__.replace(co_linetable=b'\xf8') + + +dis_bug42562 = """\ + RESUME 0 + RETURN_CONST 0 (None) +""" + +# Extended arg followed by NOP +code_bug_45757 = bytes([ + opcode.opmap['EXTENDED_ARG'], 0x01, # EXTENDED_ARG 0x01 + opcode.opmap['NOP'], 0xFF, # NOP 0xFF + opcode.opmap['EXTENDED_ARG'], 0x01, # EXTENDED_ARG 0x01 + opcode.opmap['LOAD_CONST'], 0x29, # LOAD_CONST 0x29 + opcode.opmap['RETURN_VALUE'], 0x00, # RETURN_VALUE 0x00 + ]) + +dis_bug_45757 = """\ + EXTENDED_ARG 1 + NOP + EXTENDED_ARG 1 + LOAD_CONST 297 + RETURN_VALUE """ - cls.command = (sys.executable, "-c") + +# [255, 255, 255, 252] is -4 in a 4 byte signed integer +bug46724 = bytes([ + opcode.EXTENDED_ARG, 255, + opcode.EXTENDED_ARG, 255, + opcode.EXTENDED_ARG, 255, + opcode.opmap['JUMP_FORWARD'], 252, +]) + + +dis_bug46724 = """\ + L1: EXTENDED_ARG 255 + EXTENDED_ARG 65535 + EXTENDED_ARG 16777215 + JUMP_FORWARD -4 (to L1) +""" + +def func_w_kwargs(a, b, **c): + pass + +def wrap_func_w_kwargs(): + func_w_kwargs(1, 2, c=5) + +dis_kw_names = """\ +%3d RESUME 0 + +%3d LOAD_GLOBAL 1 (func_w_kwargs + NULL) + LOAD_CONST 1 (1) + LOAD_CONST 2 (2) + LOAD_CONST 3 (5) + LOAD_CONST 4 (('c',)) + CALL_KW 3 + POP_TOP + RETURN_CONST 0 (None) +""" % (wrap_func_w_kwargs.__code__.co_firstlineno, + wrap_func_w_kwargs.__code__.co_firstlineno + 1) + +dis_intrinsic_1_2 = """\ + 0 RESUME 0 + + 1 LOAD_CONST 0 (0) + LOAD_CONST 1 (('*',)) + IMPORT_NAME 0 (math) + CALL_INTRINSIC_1 2 (INTRINSIC_IMPORT_STAR) + POP_TOP + RETURN_CONST 2 (None) +""" + +dis_intrinsic_1_5 = """\ + 0 RESUME 0 + + 1 LOAD_NAME 0 (a) + CALL_INTRINSIC_1 5 (INTRINSIC_UNARY_POSITIVE) + RETURN_VALUE +""" + +dis_intrinsic_1_6 = """\ + 0 RESUME 0 + + 1 BUILD_LIST 0 + LOAD_NAME 0 (a) + LIST_EXTEND 1 + CALL_INTRINSIC_1 6 (INTRINSIC_LIST_TO_TUPLE) + RETURN_VALUE +""" + +_BIG_LINENO_FORMAT = """\ + 1 RESUME 0 + +%3d LOAD_GLOBAL 0 (spam) + POP_TOP + RETURN_CONST 0 (None) +""" + +_BIG_LINENO_FORMAT2 = """\ + 1 RESUME 0 + +%4d LOAD_GLOBAL 0 (spam) + POP_TOP + RETURN_CONST 0 (None) +""" + +dis_module_expected_results = """\ +Disassembly of f: + 4 RESUME 0 + RETURN_CONST 0 (None) + +Disassembly of g: + 5 RESUME 0 + RETURN_CONST 0 (None) + +""" + +expr_str = "x + 1" + +dis_expr_str = """\ + 0 RESUME 0 + + 1 LOAD_NAME 0 (x) + LOAD_CONST 0 (1) + BINARY_OP 0 (+) + RETURN_VALUE +""" + +simple_stmt_str = "x = x + 1" + +dis_simple_stmt_str = """\ + 0 RESUME 0 + + 1 LOAD_NAME 0 (x) + LOAD_CONST 0 (1) + BINARY_OP 0 (+) + STORE_NAME 0 (x) + RETURN_CONST 1 (None) +""" + +annot_stmt_str = """\ + +x: int = 1 +y: fun(1) +lst[fun(0)]: int = 1 +""" +# leading newline is for a reason (tests lineno) + +dis_annot_stmt_str = """\ + 0 RESUME 0 + + 2 SETUP_ANNOTATIONS + LOAD_CONST 0 (1) + STORE_NAME 0 (x) + LOAD_NAME 1 (int) + LOAD_NAME 2 (__annotations__) + LOAD_CONST 1 ('x') + STORE_SUBSCR + + 3 LOAD_NAME 3 (fun) + PUSH_NULL + LOAD_CONST 0 (1) + CALL 1 + LOAD_NAME 2 (__annotations__) + LOAD_CONST 2 ('y') + STORE_SUBSCR + + 4 LOAD_CONST 0 (1) + LOAD_NAME 4 (lst) + LOAD_NAME 3 (fun) + PUSH_NULL + LOAD_CONST 3 (0) + CALL 1 + STORE_SUBSCR + LOAD_NAME 1 (int) + POP_TOP + RETURN_CONST 4 (None) +""" + +compound_stmt_str = """\ +x = 0 +while 1: + x += 1""" +# Trailing newline has been deliberately omitted + +dis_compound_stmt_str = """\ + 0 RESUME 0 + + 1 LOAD_CONST 0 (0) + STORE_NAME 0 (x) + + 2 NOP + + 3 L1: LOAD_NAME 0 (x) + LOAD_CONST 1 (1) + BINARY_OP 13 (+=) + STORE_NAME 0 (x) + + 2 JUMP_BACKWARD 7 (to L1) +""" + +dis_traceback = """\ +%4d RESUME 0 + +%4d NOP + +%4d L1: LOAD_CONST 1 (1) + LOAD_CONST 2 (0) + --> BINARY_OP 11 (/) + POP_TOP + +%4d L2: LOAD_FAST_CHECK 1 (tb) + RETURN_VALUE + + -- L3: PUSH_EXC_INFO + +%4d LOAD_GLOBAL 0 (Exception) + CHECK_EXC_MATCH + POP_JUMP_IF_FALSE 23 (to L7) + STORE_FAST 0 (e) + +%4d L4: LOAD_FAST 0 (e) + LOAD_ATTR 2 (__traceback__) + STORE_FAST 1 (tb) + L5: POP_EXCEPT + LOAD_CONST 0 (None) + STORE_FAST 0 (e) + DELETE_FAST 0 (e) + +%4d LOAD_FAST 1 (tb) + RETURN_VALUE + + -- L6: LOAD_CONST 0 (None) + STORE_FAST 0 (e) + DELETE_FAST 0 (e) + RERAISE 1 + +%4d L7: RERAISE 0 + + -- L8: COPY 3 + POP_EXCEPT + RERAISE 1 +ExceptionTable: + L1 to L2 -> L3 [0] + L3 to L4 -> L8 [1] lasti + L4 to L5 -> L6 [1] lasti + L6 to L8 -> L8 [1] lasti +""" % (TRACEBACK_CODE.co_firstlineno, + TRACEBACK_CODE.co_firstlineno + 1, + TRACEBACK_CODE.co_firstlineno + 2, + TRACEBACK_CODE.co_firstlineno + 5, + TRACEBACK_CODE.co_firstlineno + 3, + TRACEBACK_CODE.co_firstlineno + 4, + TRACEBACK_CODE.co_firstlineno + 5, + TRACEBACK_CODE.co_firstlineno + 3) + +def _fstring(a, b, c, d): + return f'{a} {b:4} {c!r} {d!r:4}' + +dis_fstring = """\ +%3d RESUME 0 + +%3d LOAD_FAST 0 (a) + FORMAT_SIMPLE + LOAD_CONST 1 (' ') + LOAD_FAST 1 (b) + LOAD_CONST 2 ('4') + FORMAT_WITH_SPEC + LOAD_CONST 1 (' ') + LOAD_FAST 2 (c) + CONVERT_VALUE 2 (repr) + FORMAT_SIMPLE + LOAD_CONST 1 (' ') + LOAD_FAST 3 (d) + CONVERT_VALUE 2 (repr) + LOAD_CONST 2 ('4') + FORMAT_WITH_SPEC + BUILD_STRING 7 + RETURN_VALUE +""" % (_fstring.__code__.co_firstlineno, _fstring.__code__.co_firstlineno + 1) + +def _with(c): + with c: + x = 1 + y = 2 + +dis_with = """\ +%4d RESUME 0 + +%4d LOAD_FAST 0 (c) + BEFORE_WITH + L1: POP_TOP + +%4d LOAD_CONST 1 (1) + STORE_FAST 1 (x) + +%4d L2: LOAD_CONST 0 (None) + LOAD_CONST 0 (None) + LOAD_CONST 0 (None) + CALL 2 + POP_TOP + +%4d LOAD_CONST 2 (2) + STORE_FAST 2 (y) + RETURN_CONST 0 (None) + +%4d L3: PUSH_EXC_INFO + WITH_EXCEPT_START + TO_BOOL + POP_JUMP_IF_TRUE 1 (to L4) + RERAISE 2 + L4: POP_TOP + L5: POP_EXCEPT + POP_TOP + POP_TOP + +%4d LOAD_CONST 2 (2) + STORE_FAST 2 (y) + RETURN_CONST 0 (None) + + -- L6: COPY 3 + POP_EXCEPT + RERAISE 1 +ExceptionTable: + L1 to L2 -> L3 [1] lasti + L3 to L5 -> L6 [3] lasti +""" % (_with.__code__.co_firstlineno, + _with.__code__.co_firstlineno + 1, + _with.__code__.co_firstlineno + 2, + _with.__code__.co_firstlineno + 1, + _with.__code__.co_firstlineno + 3, + _with.__code__.co_firstlineno + 1, + _with.__code__.co_firstlineno + 3, + ) + +async def _asyncwith(c): + async with c: + x = 1 + y = 2 + +dis_asyncwith = """\ +%4d RETURN_GENERATOR + POP_TOP + L1: RESUME 0 + +%4d LOAD_FAST 0 (c) + BEFORE_ASYNC_WITH + GET_AWAITABLE 1 + LOAD_CONST 0 (None) + L2: SEND 3 (to L5) + L3: YIELD_VALUE 1 + L4: RESUME 3 + JUMP_BACKWARD_NO_INTERRUPT 5 (to L2) + L5: END_SEND + L6: POP_TOP + +%4d LOAD_CONST 1 (1) + STORE_FAST 1 (x) + +%4d L7: LOAD_CONST 0 (None) + LOAD_CONST 0 (None) + LOAD_CONST 0 (None) + CALL 2 + GET_AWAITABLE 2 + LOAD_CONST 0 (None) + L8: SEND 3 (to L11) + L9: YIELD_VALUE 1 + L10: RESUME 3 + JUMP_BACKWARD_NO_INTERRUPT 5 (to L8) + L11: END_SEND + POP_TOP + +%4d LOAD_CONST 2 (2) + STORE_FAST 2 (y) + RETURN_CONST 0 (None) + +%4d L12: CLEANUP_THROW + L13: JUMP_BACKWARD_NO_INTERRUPT 25 (to L5) + L14: CLEANUP_THROW + L15: JUMP_BACKWARD_NO_INTERRUPT 9 (to L11) + L16: PUSH_EXC_INFO + WITH_EXCEPT_START + GET_AWAITABLE 2 + LOAD_CONST 0 (None) + L17: SEND 4 (to L21) + L18: YIELD_VALUE 1 + L19: RESUME 3 + JUMP_BACKWARD_NO_INTERRUPT 5 (to L17) + L20: CLEANUP_THROW + L21: END_SEND + TO_BOOL + POP_JUMP_IF_TRUE 1 (to L22) + RERAISE 2 + L22: POP_TOP + L23: POP_EXCEPT + POP_TOP + POP_TOP + +%4d LOAD_CONST 2 (2) + STORE_FAST 2 (y) + RETURN_CONST 0 (None) + + -- L24: COPY 3 + POP_EXCEPT + RERAISE 1 + L25: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + RERAISE 1 +ExceptionTable: + L1 to L3 -> L25 [0] lasti + L3 to L4 -> L12 [3] + L4 to L6 -> L25 [0] lasti + L6 to L7 -> L16 [1] lasti + L7 to L9 -> L25 [0] lasti + L9 to L10 -> L14 [2] + L10 to L13 -> L25 [0] lasti + L14 to L15 -> L25 [0] lasti + L16 to L18 -> L24 [3] lasti + L18 to L19 -> L20 [6] + L19 to L23 -> L24 [3] lasti + L23 to L25 -> L25 [0] lasti +""" % (_asyncwith.__code__.co_firstlineno, + _asyncwith.__code__.co_firstlineno + 1, + _asyncwith.__code__.co_firstlineno + 2, + _asyncwith.__code__.co_firstlineno + 1, + _asyncwith.__code__.co_firstlineno + 3, + _asyncwith.__code__.co_firstlineno + 1, + _asyncwith.__code__.co_firstlineno + 3, + ) + + +def _tryfinally(a, b): + try: + return a + finally: + b() + +def _tryfinallyconst(b): + try: + return 1 + finally: + b() + +dis_tryfinally = """\ +%4d RESUME 0 + +%4d NOP + +%4d L1: LOAD_FAST 0 (a) + +%4d L2: LOAD_FAST 1 (b) + PUSH_NULL + CALL 0 + POP_TOP + RETURN_VALUE + + -- L3: PUSH_EXC_INFO + +%4d LOAD_FAST 1 (b) + PUSH_NULL + CALL 0 + POP_TOP + RERAISE 0 + + -- L4: COPY 3 + POP_EXCEPT + RERAISE 1 +ExceptionTable: + L1 to L2 -> L3 [0] + L3 to L4 -> L4 [1] lasti +""" % (_tryfinally.__code__.co_firstlineno, + _tryfinally.__code__.co_firstlineno + 1, + _tryfinally.__code__.co_firstlineno + 2, + _tryfinally.__code__.co_firstlineno + 4, + _tryfinally.__code__.co_firstlineno + 4, + ) + +dis_tryfinallyconst = """\ +%4d RESUME 0 + +%4d NOP + +%4d NOP + +%4d LOAD_FAST 0 (b) + PUSH_NULL + CALL 0 + POP_TOP + RETURN_CONST 1 (1) + + -- L1: PUSH_EXC_INFO + +%4d LOAD_FAST 0 (b) + PUSH_NULL + CALL 0 + POP_TOP + RERAISE 0 + + -- L2: COPY 3 + POP_EXCEPT + RERAISE 1 +ExceptionTable: + L1 to L2 -> L2 [1] lasti +""" % (_tryfinallyconst.__code__.co_firstlineno, + _tryfinallyconst.__code__.co_firstlineno + 1, + _tryfinallyconst.__code__.co_firstlineno + 2, + _tryfinallyconst.__code__.co_firstlineno + 4, + _tryfinallyconst.__code__.co_firstlineno + 4, + ) + +def _g(x): + yield x + +async def _ag(x): + yield x + +async def _co(x): + async for item in _ag(x): + pass + +def _h(y): + def foo(x): + '''funcdoc''' + return list(x + z for z in y) + return foo + +dis_nested_0 = """\ + -- MAKE_CELL 0 (y) + +%4d RESUME 0 + +%4d LOAD_FAST 0 (y) + BUILD_TUPLE 1 + LOAD_CONST 1 () + MAKE_FUNCTION + SET_FUNCTION_ATTRIBUTE 8 (closure) + STORE_FAST 1 (foo) + +%4d LOAD_FAST 1 (foo) + RETURN_VALUE +""" % (_h.__code__.co_firstlineno, + _h.__code__.co_firstlineno + 1, + __file__, + _h.__code__.co_firstlineno + 1, + _h.__code__.co_firstlineno + 4, +) + +dis_nested_1 = """%s +Disassembly of : + -- COPY_FREE_VARS 1 + MAKE_CELL 0 (x) + +%4d RESUME 0 + +%4d LOAD_GLOBAL 1 (list + NULL) + LOAD_FAST 0 (x) + BUILD_TUPLE 1 + LOAD_CONST 1 ( at 0x..., file "%s", line %d>) + MAKE_FUNCTION + SET_FUNCTION_ATTRIBUTE 8 (closure) + LOAD_DEREF 1 (y) + GET_ITER + CALL 0 + CALL 1 + RETURN_VALUE +""" % (dis_nested_0, + __file__, + _h.__code__.co_firstlineno + 1, + _h.__code__.co_firstlineno + 1, + _h.__code__.co_firstlineno + 3, + __file__, + _h.__code__.co_firstlineno + 3, +) + +dis_nested_2 = """%s +Disassembly of at 0x..., file "%s", line %d>: + -- COPY_FREE_VARS 1 + +%4d RETURN_GENERATOR + POP_TOP + L1: RESUME 0 + LOAD_FAST 0 (.0) + GET_ITER + L2: FOR_ITER 10 (to L3) + STORE_FAST 1 (z) + LOAD_DEREF 2 (x) + LOAD_FAST 1 (z) + BINARY_OP 0 (+) + YIELD_VALUE 0 + RESUME 5 + POP_TOP + JUMP_BACKWARD 12 (to L2) + L3: END_FOR + POP_TOP + RETURN_CONST 0 (None) + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [0] lasti +""" % (dis_nested_1, + __file__, + _h.__code__.co_firstlineno + 3, + _h.__code__.co_firstlineno + 3, +) + +def load_test(x, y=0): + a, b = x, y + return a, b + +dis_load_test_quickened_code = """\ +%3d RESUME_CHECK 0 + +%3d LOAD_FAST_LOAD_FAST 1 (x, y) + STORE_FAST_STORE_FAST 50 (b, a) + +%3d LOAD_FAST_LOAD_FAST 35 (a, b) + BUILD_TUPLE 2 + RETURN_VALUE +""" % (load_test.__code__.co_firstlineno, + load_test.__code__.co_firstlineno + 1, + load_test.__code__.co_firstlineno + 2) + +def loop_test(): + for i in [1, 2, 3] * 3: + load_test(i) + +dis_loop_test_quickened_code = """\ +%3d RESUME_CHECK 0 + +%3d BUILD_LIST 0 + LOAD_CONST 1 ((1, 2, 3)) + LIST_EXTEND 1 + LOAD_CONST 2 (3) + BINARY_OP 5 (*) + GET_ITER + L1: FOR_ITER_LIST 14 (to L2) + STORE_FAST 0 (i) + +%3d LOAD_GLOBAL_MODULE 1 (load_test + NULL) + LOAD_FAST 0 (i) + CALL_PY_GENERAL 1 + POP_TOP + JUMP_BACKWARD 16 (to L1) + +%3d L2: END_FOR + POP_TOP + RETURN_CONST 0 (None) +""" % (loop_test.__code__.co_firstlineno, + loop_test.__code__.co_firstlineno + 1, + loop_test.__code__.co_firstlineno + 2, + loop_test.__code__.co_firstlineno + 1,) + +def extended_arg_quick(): + *_, _ = ... + +dis_extended_arg_quick_code = """\ +%3d RESUME 0 + +%3d LOAD_CONST 1 (Ellipsis) + EXTENDED_ARG 1 + UNPACK_EX 256 + POP_TOP + STORE_FAST 0 (_) + RETURN_CONST 0 (None) +"""% (extended_arg_quick.__code__.co_firstlineno, + extended_arg_quick.__code__.co_firstlineno + 1,) + +ADAPTIVE_WARMUP_DELAY = 2 + +class DisTestBase(unittest.TestCase): + "Common utilities for DisTests and TestDisTraceback" + + def strip_addresses(self, text): + return re.sub(r'\b0x[0-9A-Fa-f]+\b', '0x...', text) + + def assert_exception_table_increasing(self, lines): + prev_start, prev_end = -1, -1 + count = 0 + for line in lines: + m = re.match(r' L(\d+) to L(\d+) -> L\d+ \[\d+\]', line) + start, end = [int(g) for g in m.groups()] + self.assertGreaterEqual(end, start) + self.assertGreaterEqual(start, prev_end) + prev_start, prev_end = start, end + count += 1 + return count + + def do_disassembly_compare(self, got, expected): + if got != expected: + got = self.strip_addresses(got) + self.assertEqual(got, expected) + + +class DisTests(DisTestBase): + + maxDiff = None + + def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): + # We want to test the default printing behaviour, not the file arg + output = io.StringIO() + with contextlib.redirect_stdout(output): + if wrapper: + dis.dis(func, **kwargs) + else: + dis.disassemble(func, lasti, **kwargs) + return output.getvalue() + + def get_disassemble_as_string(self, func, lasti=-1): + return self.get_disassembly(func, lasti, False) + + def do_disassembly_test(self, func, expected, **kwargs): + self.maxDiff = None + got = self.get_disassembly(func, depth=0, **kwargs) + self.do_disassembly_compare(got, expected) + # Add checks for dis.disco + if hasattr(func, '__code__'): + got_disco = io.StringIO() + with contextlib.redirect_stdout(got_disco): + dis.disco(func.__code__, **kwargs) + self.do_disassembly_compare(got_disco.getvalue(), expected) + + def test_opmap(self): + self.assertEqual(dis.opmap["CACHE"], 0) + self.assertIn(dis.opmap["LOAD_CONST"], dis.hasconst) + self.assertIn(dis.opmap["STORE_NAME"], dis.hasname) + + def test_opname(self): + self.assertEqual(dis.opname[dis.opmap["LOAD_FAST"]], "LOAD_FAST") + + def test_boundaries(self): + self.assertEqual(dis.opmap["EXTENDED_ARG"], dis.EXTENDED_ARG) + + def test_widths(self): + long_opcodes = set(['JUMP_BACKWARD_NO_INTERRUPT', + 'INSTRUMENTED_CALL_FUNCTION_EX']) + for opcode, opname in enumerate(dis.opname): + if opname in long_opcodes or opname.startswith("INSTRUMENTED"): + continue + with self.subTest(opname=opname): + width = dis._OPNAME_WIDTH + if opcode in dis.hasarg: + width += 1 + dis._OPARG_WIDTH + self.assertLessEqual(len(opname), width) def test_dis(self): - test_code = f""" -{self.setup} -dis.dis(tested_func) -dis.dis("x = 2; print(x)") -""" - - result = subprocess.run( - self.command + (test_code,), capture_output=True - ) - self.assertNotEqual("", result.stdout.decode()) - self.assertEqual("", result.stderr.decode()) - - def test_disassemble(self): - test_code = f""" -{self.setup} -dis.disassemble(tested_func) -""" - result = subprocess.run( - self.command + (test_code,), capture_output=True - ) - # In CPython this would raise an AttributeError, not a - # TypeError because dis is implemented in python in CPython and - # as such the type mismatch wouldn't be caught immeadiately - self.assertIn("TypeError", result.stderr.decode()) - - test_code = f""" -{self.setup} -dis.disassemble(tested_func.__code__) -""" - result = subprocess.run( - self.command + (test_code,), capture_output=True - ) - self.assertNotEqual("", result.stdout.decode()) - self.assertEqual("", result.stderr.decode()) + self.do_disassembly_test(_f, dis_f) + + def test_dis_with_offsets(self): + self.do_disassembly_test(_f, dis_f_with_offsets, show_offsets=True) + + def test_bug_708901(self): + self.do_disassembly_test(bug708901, dis_bug708901) + + def test_bug_1333982(self): + # This one is checking bytecodes generated for an `assert` statement, + # so fails if the tests are run with -O. Skip this test then. + if not __debug__: + self.skipTest('need asserts, run without -O') + + self.do_disassembly_test(bug1333982, dis_bug1333982) + + def test_bug_42562(self): + self.do_disassembly_test(bug42562, dis_bug42562) + + def test_bug_45757(self): + # Extended arg followed by NOP + self.do_disassembly_test(code_bug_45757, dis_bug_45757) + + def test_bug_46724(self): + # Test that negative operargs are handled properly + self.do_disassembly_test(bug46724, dis_bug46724) + + def test_kw_names(self): + # Test that value is displayed for keyword argument names: + self.do_disassembly_test(wrap_func_w_kwargs, dis_kw_names) + + def test_intrinsic_1(self): + # Test that argrepr is displayed for CALL_INTRINSIC_1 + self.do_disassembly_test("from math import *", dis_intrinsic_1_2) + self.do_disassembly_test("+a", dis_intrinsic_1_5) + self.do_disassembly_test("(*a,)", dis_intrinsic_1_6) + + def test_intrinsic_2(self): + self.assertIn("CALL_INTRINSIC_2 1 (INTRINSIC_PREP_RERAISE_STAR)", + self.get_disassembly("try: pass\nexcept* Exception: x")) + + def test_big_linenos(self): + def func(count): + namespace = {} + func = "def foo():\n " + "".join(["\n "] * count + ["spam\n"]) + exec(func, namespace) + return namespace['foo'] + + # Test all small ranges + for i in range(1, 300): + expected = _BIG_LINENO_FORMAT % (i + 2) + self.do_disassembly_test(func(i), expected) + + # Test some larger ranges too + for i in range(300, 1000, 10): + expected = _BIG_LINENO_FORMAT % (i + 2) + self.do_disassembly_test(func(i), expected) + + for i in range(1000, 5000, 10): + expected = _BIG_LINENO_FORMAT2 % (i + 2) + self.do_disassembly_test(func(i), expected) + + from test import dis_module + self.do_disassembly_test(dis_module, dis_module_expected_results) + + def test_disassemble_str(self): + self.do_disassembly_test(expr_str, dis_expr_str) + self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str) + self.do_disassembly_test(annot_stmt_str, dis_annot_stmt_str) + self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) + + def test_disassemble_bytes(self): + self.do_disassembly_test(_f.__code__.co_code, dis_f_co_code) + + def test_disassemble_class(self): + self.do_disassembly_test(_C, dis_c) + + def test_disassemble_instance_method(self): + self.do_disassembly_test(_C(1).__init__, dis_c_instance_method) + + def test_disassemble_instance_method_bytes(self): + method_bytecode = _C(1).__init__.__code__.co_code + self.do_disassembly_test(method_bytecode, dis_c_instance_method_bytes) + + def test_disassemble_static_method(self): + self.do_disassembly_test(_C.sm, dis_c_static_method) + + def test_disassemble_class_method(self): + self.do_disassembly_test(_C.cm, dis_c_class_method) + + def test_disassemble_generator(self): + gen_func_disas = self.get_disassembly(_g) # Generator function + gen_disas = self.get_disassembly(_g(1)) # Generator iterator + self.assertEqual(gen_disas, gen_func_disas) + + def test_disassemble_async_generator(self): + agen_func_disas = self.get_disassembly(_ag) # Async generator function + agen_disas = self.get_disassembly(_ag(1)) # Async generator iterator + self.assertEqual(agen_disas, agen_func_disas) + + def test_disassemble_coroutine(self): + coro_func_disas = self.get_disassembly(_co) # Coroutine function + coro = _co(1) # Coroutine object + coro.close() # Avoid a RuntimeWarning (never awaited) + coro_disas = self.get_disassembly(coro) + self.assertEqual(coro_disas, coro_func_disas) + + def test_disassemble_fstring(self): + self.do_disassembly_test(_fstring, dis_fstring) + + def test_disassemble_with(self): + self.do_disassembly_test(_with, dis_with) + + def test_disassemble_asyncwith(self): + self.do_disassembly_test(_asyncwith, dis_asyncwith) + + def test_disassemble_try_finally(self): + self.do_disassembly_test(_tryfinally, dis_tryfinally) + self.do_disassembly_test(_tryfinallyconst, dis_tryfinallyconst) + + def test_dis_none(self): + try: + del sys.last_exc + except AttributeError: + pass + try: + del sys.last_traceback + except AttributeError: + pass + self.assertRaises(RuntimeError, dis.dis, None) + + def test_dis_traceback(self): + self.maxDiff = None + try: + del sys.last_traceback + except AttributeError: + pass + + try: + 1/0 + except Exception as e: + tb = e.__traceback__ + sys.last_exc = e + + tb_dis = self.get_disassemble_as_string(tb.tb_frame.f_code, tb.tb_lasti) + self.do_disassembly_test(None, tb_dis) + + def test_dis_object(self): + self.assertRaises(TypeError, dis.dis, object()) + + def test_disassemble_recursive(self): + def check(expected, **kwargs): + dis = self.get_disassembly(_h, **kwargs) + dis = self.strip_addresses(dis) + self.assertEqual(dis, expected) + + check(dis_nested_0, depth=0) + check(dis_nested_1, depth=1) + check(dis_nested_2, depth=2) + check(dis_nested_2, depth=3) + check(dis_nested_2, depth=None) + check(dis_nested_2) + + def test__try_compile_no_context_exc_on_error(self): + # see gh-102114 + try: + dis._try_compile(")", "") + except Exception as e: + self.assertIsNone(e.__context__) + + @staticmethod + def code_quicken(f, times=ADAPTIVE_WARMUP_DELAY): + for _ in range(times): + f() + + @cpython_only + @requires_specialization + def test_super_instructions(self): + self.code_quicken(lambda: load_test(0, 0)) + got = self.get_disassembly(load_test, adaptive=True) + self.do_disassembly_compare(got, dis_load_test_quickened_code) + + @cpython_only + @requires_specialization + def test_binary_specialize(self): + binary_op_quicken = """\ + 0 RESUME_CHECK 0 + + 1 LOAD_NAME 0 (a) + LOAD_NAME 1 (b) + %s + RETURN_VALUE +""" + co_int = compile('a + b', "", "eval") + self.code_quicken(lambda: exec(co_int, {}, {'a': 1, 'b': 2})) + got = self.get_disassembly(co_int, adaptive=True) + self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_INT 0 (+)") + + co_unicode = compile('a + b', "", "eval") + self.code_quicken(lambda: exec(co_unicode, {}, {'a': 'a', 'b': 'b'})) + got = self.get_disassembly(co_unicode, adaptive=True) + self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_UNICODE 0 (+)") + + binary_subscr_quicken = """\ + 0 RESUME_CHECK 0 + + 1 LOAD_NAME 0 (a) + LOAD_CONST 0 (0) + %s + RETURN_VALUE +""" + co_list = compile('a[0]', "", "eval") + self.code_quicken(lambda: exec(co_list, {}, {'a': [0]})) + got = self.get_disassembly(co_list, adaptive=True) + self.do_disassembly_compare(got, binary_subscr_quicken % "BINARY_SUBSCR_LIST_INT") + + co_dict = compile('a[0]', "", "eval") + self.code_quicken(lambda: exec(co_dict, {}, {'a': {0: '1'}})) + got = self.get_disassembly(co_dict, adaptive=True) + self.do_disassembly_compare(got, binary_subscr_quicken % "BINARY_SUBSCR_DICT") + + @cpython_only + @requires_specialization + def test_load_attr_specialize(self): + load_attr_quicken = """\ + 0 RESUME_CHECK 0 + + 1 LOAD_CONST 0 ('a') + LOAD_ATTR_SLOT 0 (__class__) + RETURN_VALUE +""" + co = compile("'a'.__class__", "", "eval") + self.code_quicken(lambda: exec(co, {}, {})) + got = self.get_disassembly(co, adaptive=True) + self.do_disassembly_compare(got, load_attr_quicken) + + @cpython_only + @requires_specialization + def test_call_specialize(self): + call_quicken = """\ + 0 RESUME_CHECK 0 + + 1 LOAD_NAME 0 (str) + PUSH_NULL + LOAD_CONST 0 (1) + CALL_STR_1 1 + RETURN_VALUE +""" + co = compile("str(1)", "", "eval") + self.code_quicken(lambda: exec(co, {}, {})) + got = self.get_disassembly(co, adaptive=True) + self.do_disassembly_compare(got, call_quicken) + + @cpython_only + @requires_specialization + def test_loop_quicken(self): + # Loop can trigger a quicken where the loop is located + self.code_quicken(loop_test, 4) + got = self.get_disassembly(loop_test, adaptive=True) + expected = dis_loop_test_quickened_code + self.do_disassembly_compare(got, expected) + + @cpython_only + def test_extended_arg_quick(self): + got = self.get_disassembly(extended_arg_quick) + self.do_disassembly_compare(got, dis_extended_arg_quick_code) + + def get_cached_values(self, quickened, adaptive): + def f(): + l = [] + for i in range(42): + l.append(i) + if quickened: + self.code_quicken(f) + else: + # "copy" the code to un-quicken it: + f.__code__ = f.__code__.replace() + for instruction in _unroll_caches_as_Instructions(dis.get_instructions( + f, show_caches=True, adaptive=adaptive + ), show_caches=True): + if instruction.opname == "CACHE": + yield instruction.argrepr + + @cpython_only + def test_show_caches(self): + for quickened in (False, True): + for adaptive in (False, True): + with self.subTest(f"{quickened=}, {adaptive=}"): + if adaptive: + pattern = r"^(\w+: \d+)?$" + else: + pattern = r"^(\w+: 0)?$" + caches = list(self.get_cached_values(quickened, adaptive)) + for cache in caches: + self.assertRegex(cache, pattern) + total_caches = 21 + empty_caches = 7 + self.assertEqual(caches.count(""), empty_caches) + self.assertEqual(len(caches), total_caches) + + @cpython_only + def test_show_currinstr_with_cache(self): + """ + Make sure that with lasti pointing to CACHE, it still shows the current + line correctly + """ + def f(): + print(a) + # The code above should generate a LOAD_GLOBAL which has CACHE instr after + # However, this might change in the future. So we explicitly try to find + # a CACHE entry in the instructions. If we can't do that, fail the test + + for inst in _unroll_caches_as_Instructions( + dis.get_instructions(f, show_caches=True), show_caches=True): + if inst.opname == "CACHE": + op_offset = inst.offset - 2 + cache_offset = inst.offset + break + else: + opname = inst.opname + else: + self.fail("Can't find a CACHE entry in the function provided to do the test") + + assem_op = self.get_disassembly(f.__code__, lasti=op_offset, wrapper=False) + assem_cache = self.get_disassembly(f.__code__, lasti=cache_offset, wrapper=False) + + # Make sure --> exists and points to the correct op + self.assertRegex(assem_op, fr"--> {opname}") + # Make sure when lasti points to cache, it shows the same disassembly + self.assertEqual(assem_op, assem_cache) + + +class DisWithFileTests(DisTests): + + # Run the tests again, using the file arg instead of print + def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): + output = io.StringIO() + if wrapper: + dis.dis(func, file=output, **kwargs) + else: + dis.disassemble(func, lasti, file=output, **kwargs) + return output.getvalue() + + +if dis.code_info.__doc__ is None: + code_info_consts = "0: None" +else: + code_info_consts = "0: 'Formatted details of methods, functions, or code.'" + +code_info_code_info = f"""\ +Name: code_info +Filename: (.*) +Argument count: 1 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 1 +Stack size: \\d+ +Flags: OPTIMIZED, NEWLOCALS +Constants: + {code_info_consts} +Names: + 0: _format_code_info + 1: _get_code_object +Variable names: + 0: x""" + + +@staticmethod +def tricky(a, b, /, x, y, z=True, *args, c, d, e=[], **kwds): + def f(c=c): + print(a, b, x, y, z, c, d, e, f) + yield a, b, x, y, z, c, d, e, f + +code_info_tricky = """\ +Name: tricky +Filename: (.*) +Argument count: 5 +Positional-only arguments: 2 +Kw-only arguments: 3 +Number of locals: 10 +Stack size: \\d+ +Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR +Constants: + 0: None + 1: +Variable names: + 0: a + 1: b + 2: x + 3: y + 4: z + 5: c + 6: d + 7: e + 8: args + 9: kwds +Cell variables: + 0: [abedfxyz] + 1: [abedfxyz] + 2: [abedfxyz] + 3: [abedfxyz] + 4: [abedfxyz] + 5: [abedfxyz]""" +# NOTE: the order of the cell variables above depends on dictionary order! + +co_tricky_nested_f = tricky.__func__.__code__.co_consts[1] + +code_info_tricky_nested_f = """\ +Filename: (.*) +Argument count: 1 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 1 +Stack size: \\d+ +Flags: OPTIMIZED, NEWLOCALS, NESTED +Constants: + 0: None +Names: + 0: print +Variable names: + 0: c +Free variables: + 0: [abedfxyz] + 1: [abedfxyz] + 2: [abedfxyz] + 3: [abedfxyz] + 4: [abedfxyz] + 5: [abedfxyz]""" + +code_info_expr_str = """\ +Name: +Filename: +Argument count: 0 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 0 +Stack size: \\d+ +Flags: 0x0 +Constants: + 0: 1 +Names: + 0: x""" + +code_info_simple_stmt_str = """\ +Name: +Filename: +Argument count: 0 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 0 +Stack size: \\d+ +Flags: 0x0 +Constants: + 0: 1 + 1: None +Names: + 0: x""" + +code_info_compound_stmt_str = """\ +Name: +Filename: +Argument count: 0 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 0 +Stack size: \\d+ +Flags: 0x0 +Constants: + 0: 0 + 1: 1 +Names: + 0: x""" + + +async def async_def(): + await 1 + async for a in b: pass + async with c as d: pass + +code_info_async_def = """\ +Name: async_def +Filename: (.*) +Argument count: 0 +Positional-only arguments: 0 +Kw-only arguments: 0 +Number of locals: 2 +Stack size: \\d+ +Flags: OPTIMIZED, NEWLOCALS, COROUTINE +Constants: + 0: None + 1: 1 +Names: + 0: b + 1: c +Variable names: + 0: a + 1: d""" + +class CodeInfoTests(unittest.TestCase): + test_pairs = [ + (dis.code_info, code_info_code_info), + (tricky, code_info_tricky), + (co_tricky_nested_f, code_info_tricky_nested_f), + (expr_str, code_info_expr_str), + (simple_stmt_str, code_info_simple_stmt_str), + (compound_stmt_str, code_info_compound_stmt_str), + (async_def, code_info_async_def) + ] + + def test_code_info(self): + self.maxDiff = 1000 + for x, expected in self.test_pairs: + self.assertRegex(dis.code_info(x), expected) + + def test_show_code(self): + self.maxDiff = 1000 + for x, expected in self.test_pairs: + with captured_stdout() as output: + dis.show_code(x) + self.assertRegex(output.getvalue(), expected+"\n") + output = io.StringIO() + dis.show_code(x, file=output) + self.assertRegex(output.getvalue(), expected) + + def test_code_info_object(self): + self.assertRaises(TypeError, dis.code_info, object()) + + def test_pretty_flags_no_flags(self): + self.assertEqual(dis.pretty_flags(0), '0x0') + + +# Fodder for instruction introspection tests +# Editing any of these may require recalculating the expected output +def outer(a=1, b=2): + def f(c=3, d=4): + def inner(e=5, f=6): + print(a, b, c, d, e, f) + print(a, b, c, d) + return inner + print(a, b, '', 1, [], {}, "Hello world!") + return f + +def jumpy(): + # This won't actually run (but that's OK, we only disassemble it) + for i in range(10): + print(i) + if i < 4: + continue + if i > 6: + break + else: + print("I can haz else clause?") + while i: + print(i) + i -= 1 + if i > 6: + continue + if i < 4: + break + else: + print("Who let lolcatz into this test suite?") + try: + 1 / 0 + except ZeroDivisionError: + print("Here we go, here we go, here we go...") + else: + with i as dodgy: + print("Never reach this") + finally: + print("OK, now we're done") + +# End fodder for opinfo generation tests +expected_outer_line = 1 +_line_offset = outer.__code__.co_firstlineno - 1 +code_object_f = outer.__code__.co_consts[1] +expected_f_line = code_object_f.co_firstlineno - _line_offset +code_object_inner = code_object_f.co_consts[1] +expected_inner_line = code_object_inner.co_firstlineno - _line_offset +expected_jumpy_line = 1 + +# The following lines are useful to regenerate the expected results after +# either the fodder is modified or the bytecode generation changes +# After regeneration, update the references to code_object_f and +# code_object_inner before rerunning the tests + +def _stringify_instruction(instr): + # Since line numbers and other offsets change a lot for these + # test cases, ignore them. + return f" {instr._replace(positions=None)!r}," + +def _prepare_test_cases(): + ignore = io.StringIO() + with contextlib.redirect_stdout(ignore): + f = outer() + inner = f() + _instructions_outer = dis.get_instructions(outer, first_line=expected_outer_line) + _instructions_f = dis.get_instructions(f, first_line=expected_f_line) + _instructions_inner = dis.get_instructions(inner, first_line=expected_inner_line) + _instructions_jumpy = dis.get_instructions(jumpy, first_line=expected_jumpy_line) + result = "\n".join( + [ + "expected_opinfo_outer = [", + *map(_stringify_instruction, _instructions_outer), + "]", + "", + "expected_opinfo_f = [", + *map(_stringify_instruction, _instructions_f), + "]", + "", + "expected_opinfo_inner = [", + *map(_stringify_instruction, _instructions_inner), + "]", + "", + "expected_opinfo_jumpy = [", + *map(_stringify_instruction, _instructions_jumpy), + "]", + ] + ) + result = result.replace(repr(repr(code_object_f)), "repr(code_object_f)") + result = result.replace(repr(code_object_f), "code_object_f") + result = result.replace(repr(repr(code_object_inner)), "repr(code_object_inner)") + result = result.replace(repr(code_object_inner), "code_object_inner") + print(result) + +# from test.test_dis import _prepare_test_cases; _prepare_test_cases() + +Instruction = dis.Instruction + +expected_opinfo_outer = [ + Instruction(opname='MAKE_CELL', opcode=94, arg=0, argval='a', argrepr='a', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None), + Instruction(opname='MAKE_CELL', opcode=94, arg=1, argval='b', argrepr='b', offset=2, start_offset=2, starts_line=False, line_number=None, label=None, positions=None), + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=4, start_offset=4, starts_line=True, line_number=1, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=(3, 4), argrepr='(3, 4)', offset=6, start_offset=6, starts_line=True, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='a', argrepr='a', offset=8, start_offset=8, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=1, argval='b', argrepr='b', offset=10, start_offset=10, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='BUILD_TUPLE', opcode=52, arg=2, argval=2, argrepr='', offset=12, start_offset=12, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=code_object_f, argrepr=repr(code_object_f), offset=14, start_offset=14, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='MAKE_FUNCTION', opcode=26, arg=None, argval=None, argrepr='', offset=16, start_offset=16, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=8, argval=8, argrepr='closure', offset=18, start_offset=18, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=1, argval=1, argrepr='defaults', offset=20, start_offset=20, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='STORE_FAST', opcode=110, arg=2, argval='f', argrepr='f', offset=22, start_offset=22, starts_line=False, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='print', argrepr='print + NULL', offset=24, start_offset=24, starts_line=True, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=0, argval='a', argrepr='a', offset=34, start_offset=34, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=1, argval='b', argrepr='b', offset=36, start_offset=36, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval='', argrepr="''", offset=38, start_offset=38, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=1, argrepr='1', offset=40, start_offset=40, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='BUILD_LIST', opcode=47, arg=0, argval=0, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='BUILD_MAP', opcode=48, arg=0, argval=0, argrepr='', offset=44, start_offset=44, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=4, argval='Hello world!', argrepr="'Hello world!'", offset=46, start_offset=46, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='CALL', opcode=53, arg=7, argval=7, argrepr='', offset=48, start_offset=48, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=56, start_offset=56, starts_line=False, line_number=7, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=2, argval='f', argrepr='f', offset=58, start_offset=58, starts_line=True, line_number=8, label=None, positions=None), + Instruction(opname='RETURN_VALUE', opcode=36, arg=None, argval=None, argrepr='', offset=60, start_offset=60, starts_line=False, line_number=8, label=None, positions=None), +] + +expected_opinfo_f = [ + Instruction(opname='COPY_FREE_VARS', opcode=62, arg=2, argval=2, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None), + Instruction(opname='MAKE_CELL', opcode=94, arg=0, argval='c', argrepr='c', offset=2, start_offset=2, starts_line=False, line_number=None, label=None, positions=None), + Instruction(opname='MAKE_CELL', opcode=94, arg=1, argval='d', argrepr='d', offset=4, start_offset=4, starts_line=False, line_number=None, label=None, positions=None), + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=6, start_offset=6, starts_line=True, line_number=2, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=(5, 6), argrepr='(5, 6)', offset=8, start_offset=8, starts_line=True, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=3, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=4, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='c', argrepr='c', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=1, argval='d', argrepr='d', offset=16, start_offset=16, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='BUILD_TUPLE', opcode=52, arg=4, argval=4, argrepr='', offset=18, start_offset=18, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, start_offset=20, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='MAKE_FUNCTION', opcode=26, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=8, argval=8, argrepr='closure', offset=24, start_offset=24, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=1, argval=1, argrepr='defaults', offset=26, start_offset=26, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='STORE_FAST', opcode=110, arg=2, argval='inner', argrepr='inner', offset=28, start_offset=28, starts_line=False, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=3, argval='a', argrepr='a', offset=40, start_offset=40, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=4, argval='b', argrepr='b', offset=42, start_offset=42, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=0, argval='c', argrepr='c', offset=44, start_offset=44, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=1, argval='d', argrepr='d', offset=46, start_offset=46, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='CALL', opcode=53, arg=4, argval=4, argrepr='', offset=48, start_offset=48, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=56, start_offset=56, starts_line=False, line_number=5, label=None, positions=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=2, argval='inner', argrepr='inner', offset=58, start_offset=58, starts_line=True, line_number=6, label=None, positions=None), + Instruction(opname='RETURN_VALUE', opcode=36, arg=None, argval=None, argrepr='', offset=60, start_offset=60, starts_line=False, line_number=6, label=None, positions=None), +] + +expected_opinfo_inner = [ + Instruction(opname='COPY_FREE_VARS', opcode=62, arg=4, argval=4, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None), + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=2, start_offset=2, starts_line=True, line_number=3, label=None, positions=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='print', argrepr='print + NULL', offset=4, start_offset=4, starts_line=True, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=2, argval='a', argrepr='a', offset=14, start_offset=14, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=3, argval='b', argrepr='b', offset=16, start_offset=16, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=4, argval='c', argrepr='c', offset=18, start_offset=18, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_DEREF', opcode=84, arg=5, argval='d', argrepr='d', offset=20, start_offset=20, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='LOAD_FAST_LOAD_FAST', opcode=88, arg=1, argval=('e', 'f'), argrepr='e, f', offset=22, start_offset=22, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='CALL', opcode=53, arg=6, argval=6, argrepr='', offset=24, start_offset=24, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=32, start_offset=32, starts_line=False, line_number=4, label=None, positions=None), + Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=34, start_offset=34, starts_line=False, line_number=4, label=None, positions=None), +] + +expected_opinfo_jumpy = [ + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=1, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='range', argrepr='range + NULL', offset=2, start_offset=2, starts_line=True, line_number=3, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=10, argrepr='10', offset=12, start_offset=12, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='GET_ITER', opcode=19, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='FOR_ITER', opcode=72, arg=30, argval=88, argrepr='to L4', offset=24, start_offset=24, starts_line=False, line_number=3, label=1, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=28, start_offset=28, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=4, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=40, start_offset=40, starts_line=False, line_number=4, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=4, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=4, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=52, start_offset=52, starts_line=True, line_number=5, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=54, start_offset=54, starts_line=False, line_number=5, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=56, start_offset=56, starts_line=False, line_number=5, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=68, argrepr='to L2', offset=60, start_offset=60, starts_line=False, line_number=5, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=22, argval=24, argrepr='to L1', offset=64, start_offset=64, starts_line=True, line_number=6, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=68, start_offset=68, starts_line=True, line_number=7, label=2, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=70, start_offset=70, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=72, start_offset=72, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=2, argval=84, argrepr='to L3', offset=76, start_offset=76, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=30, argval=24, argrepr='to L1', offset=80, start_offset=80, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=84, start_offset=84, starts_line=True, line_number=8, label=3, positions=None, cache_info=None), + Instruction(opname='JUMP_FORWARD', opcode=79, arg=13, argval=114, argrepr='to L5', offset=86, start_offset=86, starts_line=False, line_number=8, label=None, positions=None, cache_info=None), + Instruction(opname='END_FOR', opcode=11, arg=None, argval=None, argrepr='', offset=88, start_offset=88, starts_line=True, line_number=3, label=4, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=90, start_offset=90, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=92, start_offset=92, starts_line=True, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=102, start_offset=102, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=104, start_offset=104, starts_line=False, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=112, start_offset=112, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST_CHECK', opcode=87, arg=0, argval='i', argrepr='i', offset=114, start_offset=114, starts_line=True, line_number=11, label=5, positions=None, cache_info=None), + Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=116, start_offset=116, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=40, argval=208, argrepr='to L9', offset=124, start_offset=124, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=128, start_offset=128, starts_line=True, line_number=12, label=6, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=138, start_offset=138, starts_line=False, line_number=12, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=140, start_offset=140, starts_line=False, line_number=12, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=148, start_offset=148, starts_line=False, line_number=12, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=150, start_offset=150, starts_line=True, line_number=13, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=152, start_offset=152, starts_line=False, line_number=13, label=None, positions=None, cache_info=None), + Instruction(opname='BINARY_OP', opcode=45, arg=23, argval=23, argrepr='-=', offset=154, start_offset=154, starts_line=False, line_number=13, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=158, start_offset=158, starts_line=False, line_number=13, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=160, start_offset=160, starts_line=True, line_number=14, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=162, start_offset=162, starts_line=False, line_number=14, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=164, start_offset=164, starts_line=False, line_number=14, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=176, argrepr='to L7', offset=168, start_offset=168, starts_line=False, line_number=14, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=31, argval=114, argrepr='to L5', offset=172, start_offset=172, starts_line=True, line_number=15, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=176, start_offset=176, starts_line=True, line_number=16, label=7, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=178, start_offset=178, starts_line=False, line_number=16, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=180, start_offset=180, starts_line=False, line_number=16, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=1, argval=190, argrepr='to L8', offset=184, start_offset=184, starts_line=False, line_number=16, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_FORWARD', opcode=79, arg=20, argval=230, argrepr='to L10', offset=188, start_offset=188, starts_line=True, line_number=17, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=190, start_offset=190, starts_line=True, line_number=11, label=8, positions=None, cache_info=None), + Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=192, start_offset=192, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=208, argrepr='to L9', offset=200, start_offset=200, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=40, argval=128, argrepr='to L6', offset=204, start_offset=204, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=208, start_offset=208, starts_line=True, line_number=19, label=9, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=218, start_offset=218, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=220, start_offset=220, starts_line=False, line_number=19, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=228, start_offset=228, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), + Instruction(opname='NOP', opcode=30, arg=None, argval=None, argrepr='', offset=230, start_offset=230, starts_line=True, line_number=20, label=10, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=232, start_offset=232, starts_line=True, line_number=21, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=7, argval=0, argrepr='0', offset=234, start_offset=234, starts_line=False, line_number=21, label=None, positions=None, cache_info=None), + Instruction(opname='BINARY_OP', opcode=45, arg=11, argval=11, argrepr='/', offset=236, start_offset=236, starts_line=False, line_number=21, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=240, start_offset=240, starts_line=False, line_number=21, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=242, start_offset=242, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='BEFORE_WITH', opcode=2, arg=None, argval=None, argrepr='', offset=244, start_offset=244, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='STORE_FAST', opcode=110, arg=1, argval='dodgy', argrepr='dodgy', offset=246, start_offset=246, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=248, start_offset=248, starts_line=True, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=8, argval='Never reach this', argrepr="'Never reach this'", offset=258, start_offset=258, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=260, start_offset=260, starts_line=False, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=268, start_offset=268, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=270, start_offset=270, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=272, start_offset=272, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=274, start_offset=274, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=2, argval=2, argrepr='', offset=276, start_offset=276, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=284, start_offset=284, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=286, start_offset=286, starts_line=True, line_number=28, label=11, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=296, start_offset=296, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=298, start_offset=298, starts_line=False, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=306, start_offset=306, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=308, start_offset=308, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=310, start_offset=310, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='WITH_EXCEPT_START', opcode=44, arg=None, argval=None, argrepr='', offset=312, start_offset=312, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=314, start_offset=314, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=1, argval=328, argrepr='to L12', offset=322, start_offset=322, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='RERAISE', opcode=102, arg=2, argval=2, argrepr='', offset=326, start_offset=326, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=328, start_offset=328, starts_line=False, line_number=25, label=12, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=330, start_offset=330, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=332, start_offset=332, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=334, start_offset=334, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=26, argval=286, argrepr='to L11', offset=336, start_offset=336, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=338, start_offset=338, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=342, start_offset=342, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=346, start_offset=346, starts_line=True, line_number=22, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='CHECK_EXC_MATCH', opcode=7, arg=None, argval=None, argrepr='', offset=356, start_offset=356, starts_line=False, line_number=22, label=None, positions=None, cache_info=None), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=14, argval=390, argrepr='to L13', offset=358, start_offset=358, starts_line=False, line_number=22, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=362, start_offset=362, starts_line=False, line_number=22, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=364, start_offset=364, starts_line=True, line_number=23, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=9, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=374, start_offset=374, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=376, start_offset=376, starts_line=False, line_number=23, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=384, start_offset=384, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=386, start_offset=386, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=52, argval=286, argrepr='to L11', offset=388, start_offset=388, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=390, start_offset=390, starts_line=True, line_number=22, label=13, positions=None, cache_info=None), + Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=392, start_offset=392, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=394, start_offset=394, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=396, start_offset=396, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=398, start_offset=398, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=400, start_offset=400, starts_line=True, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=410, start_offset=410, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=412, start_offset=412, starts_line=False, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=420, start_offset=420, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=422, start_offset=422, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=424, start_offset=424, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=426, start_offset=426, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=428, start_offset=428, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), +] + +# One last piece of inspect fodder to check the default line number handling +def simple(): pass +expected_opinfo_simple = [ + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=simple.__code__.co_firstlineno, label=None, positions=None), + Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=2, start_offset=2, starts_line=False, line_number=simple.__code__.co_firstlineno, label=None), +] + + +class InstructionTestCase(BytecodeTestCase): + + def assertInstructionsEqual(self, instrs_1, instrs_2, /): + instrs_1 = [instr_1._replace(positions=None, cache_info=None) for instr_1 in instrs_1] + instrs_2 = [instr_2._replace(positions=None, cache_info=None) for instr_2 in instrs_2] + self.assertEqual(instrs_1, instrs_2) + +class InstructionTests(InstructionTestCase): + + def __init__(self, *args): + super().__init__(*args) + self.maxDiff = None + + def test_instruction_str(self): + # smoke test for __str__ + instrs = dis.get_instructions(simple) + for instr in instrs: + str(instr) + + def test_default_first_line(self): + actual = dis.get_instructions(simple) + self.assertInstructionsEqual(list(actual), expected_opinfo_simple) + + def test_first_line_set_to_None(self): + actual = dis.get_instructions(simple, first_line=None) + self.assertInstructionsEqual(list(actual), expected_opinfo_simple) + + def test_outer(self): + actual = dis.get_instructions(outer, first_line=expected_outer_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_outer) + + def test_nested(self): + with captured_stdout(): + f = outer() + actual = dis.get_instructions(f, first_line=expected_f_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_f) + + def test_doubly_nested(self): + with captured_stdout(): + inner = outer()() + actual = dis.get_instructions(inner, first_line=expected_inner_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_inner) + + def test_jumpy(self): + actual = dis.get_instructions(jumpy, first_line=expected_jumpy_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_jumpy) + + @requires_debug_ranges() + def test_co_positions(self): + code = compile('f(\n x, y, z\n)', '', 'exec') + positions = [ + instr.positions + for instr in dis.get_instructions(code) + ] + expected = [ + (0, 1, 0, 0), + (1, 1, 0, 1), + (1, 1, 0, 1), + (2, 2, 2, 3), + (2, 2, 5, 6), + (2, 2, 8, 9), + (1, 3, 0, 1), + (1, 3, 0, 1), + (1, 3, 0, 1) + ] + self.assertEqual(positions, expected) + + named_positions = [ + (pos.lineno, pos.end_lineno, pos.col_offset, pos.end_col_offset) + for pos in positions + ] + self.assertEqual(named_positions, expected) + + @requires_debug_ranges() + def test_co_positions_missing_info(self): + code = compile('x, y, z', '', 'exec') + code_without_location_table = code.replace(co_linetable=b'') + actual = dis.get_instructions(code_without_location_table) + for instruction in actual: + with self.subTest(instruction=instruction): + positions = instruction.positions + self.assertEqual(len(positions), 4) + if instruction.opname == "RESUME": + continue + self.assertIsNone(positions.lineno) + self.assertIsNone(positions.end_lineno) + self.assertIsNone(positions.col_offset) + self.assertIsNone(positions.end_col_offset) + + @requires_debug_ranges() + def test_co_positions_with_lots_of_caches(self): + def roots(a, b, c): + d = b**2 - 4 * a * c + yield (-b - cmath.sqrt(d)) / (2 * a) + if d: + yield (-b + cmath.sqrt(d)) / (2 * a) + code = roots.__code__ + ops = code.co_code[::2] + cache_opcode = opcode.opmap["CACHE"] + caches = sum(op == cache_opcode for op in ops) + non_caches = len(ops) - caches + # Make sure we have "lots of caches". If not, roots should be changed: + assert 1 / 3 <= caches / non_caches, "this test needs more caches!" + for show_caches in (False, True): + for adaptive in (False, True): + with self.subTest(f"{adaptive=}, {show_caches=}"): + co_positions = [ + positions + for op, positions in zip(ops, code.co_positions(), strict=True) + if show_caches or op != cache_opcode + ] + dis_positions = [ + None if instruction.positions is None else ( + instruction.positions.lineno, + instruction.positions.end_lineno, + instruction.positions.col_offset, + instruction.positions.end_col_offset, + ) + for instruction in _unroll_caches_as_Instructions(dis.get_instructions( + code, adaptive=adaptive, show_caches=show_caches + ), show_caches=show_caches) + ] + self.assertEqual(co_positions, dis_positions) + + def test_oparg_alias(self): + instruction = Instruction(opname="NOP", opcode=dis.opmap["NOP"], arg=None, argval=None, + argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, + positions=None) + self.assertEqual(instruction.arg, instruction.oparg) + + def test_show_caches_with_label(self): + def f(x, y, z): + if x: + res = y + else: + res = z + return res + + output = io.StringIO() + dis.dis(f.__code__, file=output, show_caches=True) + self.assertIn("L1:", output.getvalue()) + + def test_baseopname_and_baseopcode(self): + # Standard instructions + for name, code in dis.opmap.items(): + instruction = Instruction(opname=name, opcode=code, arg=None, argval=None, argrepr='', offset=0, + start_offset=0, starts_line=True, line_number=1, label=None, positions=None) + baseopname = instruction.baseopname + baseopcode = instruction.baseopcode + self.assertIsNotNone(baseopname) + self.assertIsNotNone(baseopcode) + self.assertEqual(name, baseopname) + self.assertEqual(code, baseopcode) + + # Specialized instructions + for name in opcode._specialized_opmap: + instruction = Instruction(opname=name, opcode=dis._all_opmap[name], arg=None, argval=None, argrepr='', + offset=0, start_offset=0, starts_line=True, line_number=1, label=None, positions=None) + baseopname = instruction.baseopname + baseopcode = instruction.baseopcode + self.assertIn(name, opcode._specializations[baseopname]) + self.assertEqual(opcode.opmap[baseopname], baseopcode) + + def test_jump_target(self): + # Non-jump instructions should return None + instruction = Instruction(opname="NOP", opcode=dis.opmap["NOP"], arg=None, argval=None, + argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, + positions=None) + self.assertIsNone(instruction.jump_target) + + delta = 100 + instruction = Instruction(opname="JUMP_FORWARD", opcode=dis.opmap["JUMP_FORWARD"], arg=delta, argval=delta, + argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, + positions=None) + self.assertEqual(10 + 2 + 100*2, instruction.jump_target) + + # Test negative deltas + instruction = Instruction(opname="JUMP_BACKWARD", opcode=dis.opmap["JUMP_BACKWARD"], arg=delta, argval=delta, + argrepr='', offset=200, start_offset=200, starts_line=True, line_number=1, label=None, + positions=None) + self.assertEqual(200 + 2 - 100*2 + 2*1, instruction.jump_target) + + # Make sure cache entries are handled + instruction = Instruction(opname="SEND", opcode=dis.opmap["SEND"], arg=delta, argval=delta, + argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, + positions=None) + self.assertEqual(10 + 2 + 1*2 + 100*2, instruction.jump_target) + + def test_argval_argrepr(self): + def f(opcode, oparg, offset, *init_args): + arg_resolver = dis.ArgResolver(*init_args) + return arg_resolver.get_argval_argrepr(opcode, oparg, offset) + + offset = 42 + co_consts = (0, 1, 2, 3) + names = {1: 'a', 2: 'b'} + varname_from_oparg = lambda i : names[i] + labels_map = {24: 1} + args = (offset, co_consts, names, varname_from_oparg, labels_map) + self.assertEqual(f(opcode.opmap["POP_TOP"], None, *args), (None, '')) + self.assertEqual(f(opcode.opmap["LOAD_CONST"], 1, *args), (1, '1')) + self.assertEqual(f(opcode.opmap["LOAD_GLOBAL"], 2, *args), ('a', 'a')) + self.assertEqual(f(opcode.opmap["JUMP_BACKWARD"], 11, *args), (24, 'to L1')) + self.assertEqual(f(opcode.opmap["COMPARE_OP"], 3, *args), ('<', '<')) + self.assertEqual(f(opcode.opmap["SET_FUNCTION_ATTRIBUTE"], 2, *args), (2, 'kwdefaults')) + self.assertEqual(f(opcode.opmap["BINARY_OP"], 3, *args), (3, '<<')) + self.assertEqual(f(opcode.opmap["CALL_INTRINSIC_1"], 2, *args), (2, 'INTRINSIC_IMPORT_STAR')) + + def test_custom_arg_resolver(self): + class MyArgResolver(dis.ArgResolver): + def offset_from_jump_arg(self, op, arg, offset): + return arg + 1 + + def get_label_for_offset(self, offset): + return 2 * offset + + def f(opcode, oparg, offset, *init_args): + arg_resolver = MyArgResolver(*init_args) + return arg_resolver.get_argval_argrepr(opcode, oparg, offset) + offset = 42 + self.assertEqual(f(opcode.opmap["JUMP_BACKWARD"], 1, offset), (2, 'to L4')) + self.assertEqual(f(opcode.opmap["SETUP_FINALLY"], 2, offset), (3, 'to L6')) + + + def get_instructions(self, code): + return dis._get_instructions_bytes(code) + + def test_start_offset(self): + # When no extended args are present, + # start_offset should be equal to offset + + instructions = list(dis.Bytecode(_f)) + for instruction in instructions: + self.assertEqual(instruction.offset, instruction.start_offset) + + def last_item(iterable): + return functools.reduce(lambda a, b : b, iterable) + + code = bytes([ + opcode.opmap["LOAD_FAST"], 0x00, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, + ]) + labels_map = dis._make_labels_map(code) + jump = last_item(self.get_instructions(code)) + self.assertEqual(4, jump.offset) + self.assertEqual(2, jump.start_offset) + + code = bytes([ + opcode.opmap["LOAD_FAST"], 0x00, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, + opcode.opmap["CACHE"], 0x00, + ]) + jump = last_item(self.get_instructions(code)) + self.assertEqual(8, jump.offset) + self.assertEqual(2, jump.start_offset) + + code = bytes([ + opcode.opmap["LOAD_FAST"], 0x00, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["EXTENDED_ARG"], 0x01, + opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, + opcode.opmap["CACHE"], 0x00, + ]) + instructions = list(self.get_instructions(code)) + # 1st jump + self.assertEqual(4, instructions[2].offset) + self.assertEqual(2, instructions[2].start_offset) + # 2nd jump + self.assertEqual(14, instructions[6].offset) + self.assertEqual(8, instructions[6].start_offset) + + def test_cache_offset_and_end_offset(self): + code = bytes([ + opcode.opmap["LOAD_GLOBAL"], 0x01, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["LOAD_FAST"], 0x00, + opcode.opmap["CALL"], 0x01, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00, + opcode.opmap["CACHE"], 0x00 + ]) + instructions = list(self.get_instructions(code)) + self.assertEqual(2, instructions[0].cache_offset) + self.assertEqual(10, instructions[0].end_offset) + self.assertEqual(12, instructions[1].cache_offset) + self.assertEqual(12, instructions[1].end_offset) + self.assertEqual(14, instructions[2].cache_offset) + self.assertEqual(20, instructions[2].end_offset) + + # end_offset of the previous instruction should be equal to the + # start_offset of the following instruction + instructions = list(dis.Bytecode(self.test_cache_offset_and_end_offset)) + for prev, curr in zip(instructions, instructions[1:]): + self.assertEqual(prev.end_offset, curr.start_offset) + + +# get_instructions has its own tests above, so can rely on it to validate +# the object oriented API +class BytecodeTests(InstructionTestCase, DisTestBase): + + def test_instantiation(self): + # Test with function, method, code string and code object + for obj in [_f, _C(1).__init__, "a=1", _f.__code__]: + with self.subTest(obj=obj): + b = dis.Bytecode(obj) + self.assertIsInstance(b.codeobj, types.CodeType) + + self.assertRaises(TypeError, dis.Bytecode, object()) + + def test_iteration(self): + for obj in [_f, _C(1).__init__, "a=1", _f.__code__]: + with self.subTest(obj=obj): + via_object = list(dis.Bytecode(obj)) + via_generator = list(dis.get_instructions(obj)) + self.assertInstructionsEqual(via_object, via_generator) + + def test_explicit_first_line(self): + actual = dis.Bytecode(outer, first_line=expected_outer_line) + self.assertInstructionsEqual(list(actual), expected_opinfo_outer) + + def test_source_line_in_disassembly(self): + # Use the line in the source code + actual = dis.Bytecode(simple).dis() + actual = actual.strip().partition(" ")[0] # extract the line no + expected = str(simple.__code__.co_firstlineno) + self.assertEqual(actual, expected) + # Use an explicit first line number + actual = dis.Bytecode(simple, first_line=350).dis() + actual = actual.strip().partition(" ")[0] # extract the line no + self.assertEqual(actual, "350") + + def test_info(self): + self.maxDiff = 1000 + for x, expected in CodeInfoTests.test_pairs: + b = dis.Bytecode(x) + self.assertRegex(b.info(), expected) + + def test_disassembled(self): + actual = dis.Bytecode(_f).dis() + self.do_disassembly_compare(actual, dis_f) + + def test_from_traceback(self): + tb = get_tb() + b = dis.Bytecode.from_traceback(tb) + while tb.tb_next: tb = tb.tb_next + + self.assertEqual(b.current_offset, tb.tb_lasti) + + def test_from_traceback_dis(self): + self.maxDiff = None + tb = get_tb() + b = dis.Bytecode.from_traceback(tb) + self.assertEqual(b.dis(), dis_traceback) + + @requires_debug_ranges() + def test_bytecode_co_positions(self): + bytecode = dis.Bytecode("a=1") + for instr, positions in zip(bytecode, bytecode.codeobj.co_positions()): + assert instr.positions == positions + +class TestBytecodeTestCase(BytecodeTestCase): + def test_assert_not_in_with_op_not_in_bytecode(self): + code = compile("a = 1", "", "exec") + self.assertInBytecode(code, "LOAD_CONST", 1) + self.assertNotInBytecode(code, "LOAD_NAME") + self.assertNotInBytecode(code, "LOAD_NAME", "a") + + def test_assert_not_in_with_arg_not_in_bytecode(self): + code = compile("a = 1", "", "exec") + self.assertInBytecode(code, "LOAD_CONST") + self.assertInBytecode(code, "LOAD_CONST", 1) + self.assertNotInBytecode(code, "LOAD_CONST", 2) + + def test_assert_not_in_with_arg_in_bytecode(self): + code = compile("a = 1", "", "exec") + with self.assertRaises(AssertionError): + self.assertNotInBytecode(code, "LOAD_CONST", 1) + +class TestFinderMethods(unittest.TestCase): + def test__find_imports(self): + cases = [ + ("import a.b.c", ('a.b.c', 0, None)), + ("from a.b import c", ('a.b', 0, ('c',))), + ("from a.b import c as d", ('a.b', 0, ('c',))), + ("from a.b import *", ('a.b', 0, ('*',))), + ("from ...a.b import c as d", ('a.b', 3, ('c',))), + ("from ..a.b import c as d, e as f", ('a.b', 2, ('c', 'e'))), + ("from ..a.b import *", ('a.b', 2, ('*',))), + ] + for src, expected in cases: + with self.subTest(src=src): + code = compile(src, "", "exec") + res = tuple(dis._find_imports(code)) + self.assertEqual(len(res), 1) + self.assertEqual(res[0], expected) + + def test__find_store_names(self): + cases = [ + ("x+y", ()), + ("x=y=1", ('x', 'y')), + ("x+=y", ('x',)), + ("global x\nx=y=1", ('x', 'y')), + ("global x\nz=x", ('z',)), + ] + for src, expected in cases: + with self.subTest(src=src): + code = compile(src, "", "exec") + res = tuple(dis._find_store_names(code)) + self.assertEqual(res, expected) + + def test_findlabels(self): + labels = dis.findlabels(jumpy.__code__.co_code) + jumps = [ + instr.offset + for instr in expected_opinfo_jumpy + if instr.is_jump_target + ] + + self.assertEqual(sorted(labels), sorted(jumps)) + + def test_findlinestarts(self): + def func(): + pass + + code = func.__code__ + offsets = [linestart[0] for linestart in dis.findlinestarts(code)] + self.assertEqual(offsets, [0, 2]) + + +class TestDisTraceback(DisTestBase): + def setUp(self) -> None: + try: # We need to clean up existing tracebacks + del sys.last_exc + except AttributeError: + pass + try: # We need to clean up existing tracebacks + del sys.last_traceback + except AttributeError: + pass + return super().setUp() + + def get_disassembly(self, tb): + output = io.StringIO() + with contextlib.redirect_stdout(output): + dis.distb(tb) + return output.getvalue() + + def test_distb_empty(self): + with self.assertRaises(RuntimeError): + dis.distb() + + def test_distb_last_traceback(self): + self.maxDiff = None + # We need to have an existing last traceback in `sys`: + tb = get_tb() + sys.last_traceback = tb + + self.do_disassembly_compare(self.get_disassembly(None), dis_traceback) + + def test_distb_explicit_arg(self): + self.maxDiff = None + tb = get_tb() + + self.do_disassembly_compare(self.get_disassembly(tb), dis_traceback) + + +class TestDisTracebackWithFile(TestDisTraceback): + # Run the `distb` tests again, using the file arg instead of print + def get_disassembly(self, tb): + output = io.StringIO() + with contextlib.redirect_stdout(output): + dis.distb(tb, file=output) + return output.getvalue() + +def _unroll_caches_as_Instructions(instrs, show_caches=False): + # Cache entries are no longer reported by dis as fake instructions, + # but some tests assume that do. We should rewrite the tests to assume + # the new API, but it will be clearer to keep the tests working as + # before and do that in a separate PR. + + for instr in instrs: + yield instr + if not show_caches: + continue + + offset = instr.offset + for name, size, data in (instr.cache_info or ()): + for i in range(size): + offset += 2 + # Only show the fancy argrepr for a CACHE instruction when it's + # the first entry for a particular cache value: + if i == 0: + argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" + else: + argrepr = "" + + yield Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, + False, None, None, instr.positions) + + +class TestDisCLI(unittest.TestCase): + + def setUp(self): + self.filename = tempfile.mktemp() + self.addCleanup(os_helper.unlink, self.filename) + + @staticmethod + def text_normalize(string): + """Dedent *string* and strip it from its surrounding whitespaces. + + This method is used by the other utility functions so that any + string to write or to match against can be freely indented. + """ + return textwrap.dedent(string).strip() + + def set_source(self, content): + with open(self.filename, 'w') as fp: + fp.write(self.text_normalize(content)) + + def invoke_dis(self, *flags): + output = io.StringIO() + with contextlib.redirect_stdout(output): + dis.main(args=[*flags, self.filename]) + return self.text_normalize(output.getvalue()) + + def check_output(self, source, expect, *flags): + with self.subTest(source=source, flags=flags): + self.set_source(source) + res = self.invoke_dis(*flags) + expect = self.text_normalize(expect) + self.assertListEqual(res.splitlines(), expect.splitlines()) + + def test_invocation(self): + # test various combinations of parameters + base_flags = [ + ('-C', '--show-caches'), + ('-O', '--show-offsets'), + ] + + self.set_source(''' + def f(): + print(x) + return None + ''') + + for r in range(1, len(base_flags) + 1): + for choices in itertools.combinations(base_flags, r=r): + for args in itertools.product(*choices): + with self.subTest(args=args[1:]): + _ = self.invoke_dis(*args) + + with self.assertRaises(SystemExit): + # suppress argparse error message + with contextlib.redirect_stderr(io.StringIO()): + _ = self.invoke_dis('--unknown') + + def test_show_cache(self): + # test 'python -m dis -C/--show-caches' + source = 'print()' + expect = ''' + 0 RESUME 0 + + 1 LOAD_NAME 0 (print) + PUSH_NULL + CALL 0 + CACHE 0 (counter: 0) + CACHE 0 (func_version: 0) + CACHE 0 + POP_TOP + RETURN_CONST 0 (None) + ''' + for flag in ['-C', '--show-caches']: + self.check_output(source, expect, flag) + + def test_show_offsets(self): + # test 'python -m dis -O/--show-offsets' + source = 'pass' + expect = ''' + 0 0 RESUME 0 + + 1 2 RETURN_CONST 0 (None) + ''' + for flag in ['-O', '--show-offsets']: + self.check_output(source, expect, flag) if __name__ == "__main__": From 45ab22954a6ca6aa9e8f5f3a2f49b3b975900317 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:10:42 +0200 Subject: [PATCH 07/16] Update `support/bytecode_helper.py` from 3.13.7 --- Lib/test/support/bytecode_helper.py | 109 ++++++++++++++++------------ 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/Lib/test/support/bytecode_helper.py b/Lib/test/support/bytecode_helper.py index 388d126677..85bcd1f0f1 100644 --- a/Lib/test/support/bytecode_helper.py +++ b/Lib/test/support/bytecode_helper.py @@ -3,10 +3,26 @@ import unittest import dis import io -from _testinternalcapi import compiler_codegen, optimize_cfg, assemble_code_object +import opcode +try: + import _testinternalcapi +except ImportError: + _testinternalcapi = None _UNSPECIFIED = object() +def instructions_with_positions(instrs, co_positions): + # Return (instr, positions) pairs from the instrs list and co_positions + # iterator. The latter contains items for cache lines and the former + # doesn't, so those need to be skipped. + + co_positions = co_positions or iter(()) + for instr in instrs: + yield instr, next(co_positions, ()) + for _, size, _ in (instr.cache_info or ()): + for i in range(size): + next(co_positions, ()) + class BytecodeTestCase(unittest.TestCase): """Custom assertion methods for inspecting bytecode.""" @@ -53,16 +69,14 @@ class CompilationStepTestCase(unittest.TestCase): class Label: pass - def assertInstructionsMatch(self, actual_, expected_): - # get two lists where each entry is a label or - # an instruction tuple. Normalize the labels to the - # instruction count of the target, and compare the lists. - - self.assertIsInstance(actual_, list) - self.assertIsInstance(expected_, list) + def assertInstructionsMatch(self, actual_seq, expected): + # get an InstructionSequence and an expected list, where each + # entry is a label or an instruction tuple. Construct an expcted + # instruction sequence and compare with the one given. - actual = self.normalize_insts(actual_) - expected = self.normalize_insts(expected_) + self.assertIsInstance(expected, list) + actual = actual_seq.get_instructions() + expected = self.seq_from_insts(expected).get_instructions() self.assertEqual(len(actual), len(expected)) # compare instructions @@ -72,10 +86,8 @@ def assertInstructionsMatch(self, actual_, expected_): continue self.assertIsInstance(exp, tuple) self.assertIsInstance(act, tuple) - # crop comparison to the provided expected values - if len(act) > len(exp): - act = act[:len(exp)] - self.assertEqual(exp, act) + idx = max([p[0] for p in enumerate(exp) if p[1] != -1]) + self.assertEqual(exp[:idx], act[:idx]) def resolveAndRemoveLabels(self, insts): idx = 0 @@ -90,54 +102,57 @@ def resolveAndRemoveLabels(self, insts): return res - def normalize_insts(self, insts): - """ Map labels to instruction index. - Map opcodes to opnames. - """ - insts = self.resolveAndRemoveLabels(insts) - res = [] - for item in insts: - assert isinstance(item, tuple) - opcode, oparg, *loc = item - opcode = dis.opmap.get(opcode, opcode) - if isinstance(oparg, self.Label): - arg = oparg.value - else: - arg = oparg if opcode in self.HAS_ARG else None - opcode = dis.opname[opcode] - res.append((opcode, arg, *loc)) - return res + def seq_from_insts(self, insts): + labels = {item for item in insts if isinstance(item, self.Label)} + for i, lbl in enumerate(labels): + lbl.value = i - def complete_insts_info(self, insts): - # fill in omitted fields in location, and oparg 0 for ops with no arg. - res = [] + seq = _testinternalcapi.new_instruction_sequence() for item in insts: - assert isinstance(item, tuple) - inst = list(item) - opcode = dis.opmap[inst[0]] - oparg = inst[1] - loc = inst[2:] + [-1] * (6 - len(inst)) - res.append((opcode, oparg, *loc)) - return res + if isinstance(item, self.Label): + seq.use_label(item.value) + else: + op = item[0] + if isinstance(op, str): + op = opcode.opmap[op] + arg, *loc = item[1:] + if isinstance(arg, self.Label): + arg = arg.value + loc = loc + [-1] * (4 - len(loc)) + seq.addop(op, arg or 0, *loc) + return seq + + def check_instructions(self, insts): + for inst in insts: + if isinstance(inst, self.Label): + continue + op, arg, *loc = inst + if isinstance(op, str): + op = opcode.opmap[op] + self.assertEqual(op in opcode.hasarg, + arg is not None, + f"{opcode.opname[op]=} {arg=}") + self.assertTrue(all(isinstance(l, int) for l in loc)) +@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") class CodegenTestCase(CompilationStepTestCase): def generate_code(self, ast): - insts, _ = compiler_codegen(ast, "my_file.py", 0) + insts, _ = _testinternalcapi.compiler_codegen(ast, "my_file.py", 0) return insts +@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") class CfgOptimizationTestCase(CompilationStepTestCase): - def get_optimized(self, insts, consts, nlocals=0): - insts = self.normalize_insts(insts) - insts = self.complete_insts_info(insts) - insts = optimize_cfg(insts, consts, nlocals) + def get_optimized(self, seq, consts, nlocals=0): + insts = _testinternalcapi.optimize_cfg(seq, consts, nlocals) return insts, consts +@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") class AssemblerTestCase(CompilationStepTestCase): def get_code_object(self, filename, insts, metadata): - co = assemble_code_object(filename, insts, metadata) + co = _testinternalcapi.assemble_code_object(filename, insts, metadata) return co From 422456387515bf0d3b031e7ca5a84b6ef5a6b27f Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:18:07 +0200 Subject: [PATCH 08/16] correct is_valid --- stdlib/src/opcode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/src/opcode.rs b/stdlib/src/opcode.rs index 76c28d1074..7eee6d107c 100644 --- a/stdlib/src/opcode.rs +++ b/stdlib/src/opcode.rs @@ -41,7 +41,7 @@ mod opcode { /// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/internal/pycore_opcode_metadata.h#L914-L916 #[must_use] pub const fn is_valid(opcode: i32) -> bool { - opcode >= 0 && opcode < 268 + opcode >= 0 && opcode < 268 && opcode != 255 } // All `has_*` methods below mimics From af92d5598734de3d7d852cdbcfd8b5f8dc831d91 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:18:21 +0200 Subject: [PATCH 09/16] Patch failing tests --- Lib/test/test__opcode.py | 2 ++ Lib/test/test_dis.py | 69 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index 10f04b64dd..dd4f30ab17 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -56,6 +56,7 @@ def check_function(self, func, expected): class StackEffectTests(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON def test_stack_effect(self): self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1) self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1) @@ -76,6 +77,7 @@ def test_stack_effect(self): self.assertRaises(ValueError, stack_effect, code) self.assertRaises(ValueError, stack_effect, code, 0) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_stack_effect_jump(self): FOR_ITER = dis.opmap['FOR_ITER'] self.assertEqual(stack_effect(FOR_ITER, 0), 1) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 73807d7fa9..f9645d428e 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -205,7 +205,8 @@ def bug42562(): # Set line number for 'pass' to None -bug42562.__code__ = bug42562.__code__.replace(co_linetable=b'\xf8') +# TODO: RUSTPYTHON; AttributeError: attribute '__code__' of 'function' objects is not writable +# bug42562.__code__ = bug42562.__code__.replace(co_linetable=b'\xf8') dis_bug42562 = """\ @@ -949,15 +950,19 @@ def test_widths(self): width += 1 + dis._OPARG_WIDTH self.assertLessEqual(len(opname), width) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_dis(self): self.do_disassembly_test(_f, dis_f) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_dis_with_offsets(self): self.do_disassembly_test(_f, dis_f_with_offsets, show_offsets=True) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_bug_708901(self): self.do_disassembly_test(bug708901, dis_bug708901) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_bug_1333982(self): # This one is checking bytecodes generated for an `assert` statement, # so fails if the tests are run with -O. Skip this test then. @@ -966,6 +971,7 @@ def test_bug_1333982(self): self.do_disassembly_test(bug1333982, dis_bug1333982) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_bug_42562(self): self.do_disassembly_test(bug42562, dis_bug42562) @@ -977,20 +983,24 @@ def test_bug_46724(self): # Test that negative operargs are handled properly self.do_disassembly_test(bug46724, dis_bug46724) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_kw_names(self): # Test that value is displayed for keyword argument names: self.do_disassembly_test(wrap_func_w_kwargs, dis_kw_names) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_intrinsic_1(self): # Test that argrepr is displayed for CALL_INTRINSIC_1 self.do_disassembly_test("from math import *", dis_intrinsic_1_2) self.do_disassembly_test("+a", dis_intrinsic_1_5) self.do_disassembly_test("(*a,)", dis_intrinsic_1_6) + @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: rustPython does not implement this feature yet def test_intrinsic_2(self): self.assertIn("CALL_INTRINSIC_2 1 (INTRINSIC_PREP_RERAISE_STAR)", self.get_disassembly("try: pass\nexcept* Exception: x")) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_big_linenos(self): def func(count): namespace = {} @@ -1015,41 +1025,51 @@ def func(count): from test import dis_module self.do_disassembly_test(dis_module, dis_module_expected_results) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_str(self): self.do_disassembly_test(expr_str, dis_expr_str) self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str) self.do_disassembly_test(annot_stmt_str, dis_annot_stmt_str) self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_disassemble_bytes(self): self.do_disassembly_test(_f.__code__.co_code, dis_f_co_code) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_class(self): self.do_disassembly_test(_C, dis_c) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_instance_method(self): self.do_disassembly_test(_C(1).__init__, dis_c_instance_method) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_disassemble_instance_method_bytes(self): method_bytecode = _C(1).__init__.__code__.co_code self.do_disassembly_test(method_bytecode, dis_c_instance_method_bytes) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_static_method(self): self.do_disassembly_test(_C.sm, dis_c_static_method) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_class_method(self): self.do_disassembly_test(_C.cm, dis_c_class_method) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_generator(self): gen_func_disas = self.get_disassembly(_g) # Generator function gen_disas = self.get_disassembly(_g(1)) # Generator iterator self.assertEqual(gen_disas, gen_func_disas) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_async_generator(self): agen_func_disas = self.get_disassembly(_ag) # Async generator function agen_disas = self.get_disassembly(_ag(1)) # Async generator iterator self.assertEqual(agen_disas, agen_func_disas) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_coroutine(self): coro_func_disas = self.get_disassembly(_co) # Coroutine function coro = _co(1) # Coroutine object @@ -1057,15 +1077,19 @@ def test_disassemble_coroutine(self): coro_disas = self.get_disassembly(coro) self.assertEqual(coro_disas, coro_func_disas) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_fstring(self): self.do_disassembly_test(_fstring, dis_fstring) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_with(self): self.do_disassembly_test(_with, dis_with) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_asyncwith(self): self.do_disassembly_test(_asyncwith, dis_asyncwith) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_try_finally(self): self.do_disassembly_test(_tryfinally, dis_tryfinally) self.do_disassembly_test(_tryfinallyconst, dis_tryfinallyconst) @@ -1081,6 +1105,7 @@ def test_dis_none(self): pass self.assertRaises(RuntimeError, dis.dis, None) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_dis_traceback(self): self.maxDiff = None try: @@ -1100,6 +1125,7 @@ def test_dis_traceback(self): def test_dis_object(self): self.assertRaises(TypeError, dis.dis, object()) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassemble_recursive(self): def check(expected, **kwargs): dis = self.get_disassembly(_h, **kwargs) @@ -1459,11 +1485,13 @@ class CodeInfoTests(unittest.TestCase): (async_def, code_info_async_def) ] + @unittest.expectedFailure # TODO: RUSTPYTHON def test_code_info(self): self.maxDiff = 1000 for x, expected in self.test_pairs: self.assertRegex(dis.code_info(x), expected) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_show_code(self): self.maxDiff = 1000 for x, expected in self.test_pairs: @@ -1525,9 +1553,12 @@ def jumpy(): expected_outer_line = 1 _line_offset = outer.__code__.co_firstlineno - 1 code_object_f = outer.__code__.co_consts[1] -expected_f_line = code_object_f.co_firstlineno - _line_offset -code_object_inner = code_object_f.co_consts[1] -expected_inner_line = code_object_inner.co_firstlineno - _line_offset +# TODO: RUSTPYTHON; AttributeError: 'int' object has no attribute 'co_firstlineno' +# expected_f_line = code_object_f.co_firstlineno - _line_offset +# TODO: RUSTPYTHON; AttributeError: 'int' object has no attribute 'co_consts' +#code_object_inner = code_object_f.co_consts[1] +# TODO: RUSTPYTHON; NameError: name 'code_object_inner' is not defined +# expected_inner_line = code_object_inner.co_firstlineno - _line_offset expected_jumpy_line = 1 # The following lines are useful to regenerate the expected results after @@ -1616,7 +1647,8 @@ def _prepare_test_cases(): Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='c', argrepr='c', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None), Instruction(opname='LOAD_FAST', opcode=85, arg=1, argval='d', argrepr='d', offset=16, start_offset=16, starts_line=False, line_number=3, label=None, positions=None), Instruction(opname='BUILD_TUPLE', opcode=52, arg=4, argval=4, argrepr='', offset=18, start_offset=18, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, start_offset=20, starts_line=False, line_number=3, label=None, positions=None), + # TODO: RUSTPYTHON; NameError: name 'code_object_inner' is not defined + #Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, start_offset=20, starts_line=False, line_number=3, label=None, positions=None), Instruction(opname='MAKE_FUNCTION', opcode=26, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None), Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=8, argval=8, argrepr='closure', offset=24, start_offset=24, starts_line=False, line_number=3, label=None, positions=None), Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=1, argval=1, argrepr='defaults', offset=26, start_offset=26, starts_line=False, line_number=3, label=None, positions=None), @@ -1787,36 +1819,43 @@ def __init__(self, *args): super().__init__(*args) self.maxDiff = None + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_instruction_str(self): # smoke test for __str__ instrs = dis.get_instructions(simple) for instr in instrs: str(instr) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_default_first_line(self): actual = dis.get_instructions(simple) self.assertInstructionsEqual(list(actual), expected_opinfo_simple) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_first_line_set_to_None(self): actual = dis.get_instructions(simple, first_line=None) self.assertInstructionsEqual(list(actual), expected_opinfo_simple) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_outer(self): actual = dis.get_instructions(outer, first_line=expected_outer_line) self.assertInstructionsEqual(list(actual), expected_opinfo_outer) + @unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'expected_f_line' is not defined def test_nested(self): with captured_stdout(): f = outer() actual = dis.get_instructions(f, first_line=expected_f_line) self.assertInstructionsEqual(list(actual), expected_opinfo_f) + @unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'expected_inner_line' is not defined def test_doubly_nested(self): with captured_stdout(): inner = outer()() actual = dis.get_instructions(inner, first_line=expected_inner_line) self.assertInstructionsEqual(list(actual), expected_opinfo_inner) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_jumpy(self): actual = dis.get_instructions(jumpy, first_line=expected_jumpy_line) self.assertInstructionsEqual(list(actual), expected_opinfo_jumpy) @@ -1904,6 +1943,7 @@ def test_oparg_alias(self): positions=None) self.assertEqual(instruction.arg, instruction.oparg) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_show_caches_with_label(self): def f(x, y, z): if x: @@ -2001,6 +2041,7 @@ def f(opcode, oparg, offset, *init_args): def get_instructions(self, code): return dis._get_instructions_bytes(code) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_start_offset(self): # When no extended args are present, # start_offset should be equal to offset @@ -2053,6 +2094,7 @@ def last_item(iterable): self.assertEqual(14, instructions[6].offset) self.assertEqual(8, instructions[6].start_offset) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_cache_offset_and_end_offset(self): code = bytes([ opcode.opmap["LOAD_GLOBAL"], 0x01, @@ -2094,6 +2136,7 @@ def test_instantiation(self): self.assertRaises(TypeError, dis.Bytecode, object()) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_iteration(self): for obj in [_f, _C(1).__init__, "a=1", _f.__code__]: with self.subTest(obj=obj): @@ -2101,10 +2144,12 @@ def test_iteration(self): via_generator = list(dis.get_instructions(obj)) self.assertInstructionsEqual(via_object, via_generator) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_explicit_first_line(self): actual = dis.Bytecode(outer, first_line=expected_outer_line) self.assertInstructionsEqual(list(actual), expected_opinfo_outer) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_source_line_in_disassembly(self): # Use the line in the source code actual = dis.Bytecode(simple).dis() @@ -2116,12 +2161,14 @@ def test_source_line_in_disassembly(self): actual = actual.strip().partition(" ")[0] # extract the line no self.assertEqual(actual, "350") + @unittest.expectedFailure # TODO: RUSTPYTHON def test_info(self): self.maxDiff = 1000 for x, expected in CodeInfoTests.test_pairs: b = dis.Bytecode(x) self.assertRegex(b.info(), expected) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_disassembled(self): actual = dis.Bytecode(_f).dis() self.do_disassembly_compare(actual, dis_f) @@ -2133,6 +2180,7 @@ def test_from_traceback(self): self.assertEqual(b.current_offset, tb.tb_lasti) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_from_traceback_dis(self): self.maxDiff = None tb = get_tb() @@ -2146,24 +2194,28 @@ def test_bytecode_co_positions(self): assert instr.positions == positions class TestBytecodeTestCase(BytecodeTestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_assert_not_in_with_op_not_in_bytecode(self): code = compile("a = 1", "", "exec") self.assertInBytecode(code, "LOAD_CONST", 1) self.assertNotInBytecode(code, "LOAD_NAME") self.assertNotInBytecode(code, "LOAD_NAME", "a") + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_assert_not_in_with_arg_not_in_bytecode(self): code = compile("a = 1", "", "exec") self.assertInBytecode(code, "LOAD_CONST") self.assertInBytecode(code, "LOAD_CONST", 1) self.assertNotInBytecode(code, "LOAD_CONST", 2) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_assert_not_in_with_arg_in_bytecode(self): code = compile("a = 1", "", "exec") with self.assertRaises(AssertionError): self.assertNotInBytecode(code, "LOAD_CONST", 1) class TestFinderMethods(unittest.TestCase): + @unittest.expectedFailure # TODO: RUSTPYTHON def test__find_imports(self): cases = [ ("import a.b.c", ('a.b.c', 0, None)), @@ -2181,6 +2233,7 @@ def test__find_imports(self): self.assertEqual(len(res), 1) self.assertEqual(res[0], expected) + @unittest.expectedFailure # TODO: RUSTPYTHON def test__find_store_names(self): cases = [ ("x+y", ()), @@ -2195,6 +2248,7 @@ def test__find_store_names(self): res = tuple(dis._find_store_names(code)) self.assertEqual(res, expected) + @unittest.expectedFailure # TODO: RUSTPYTHON def test_findlabels(self): labels = dis.findlabels(jumpy.__code__.co_code) jumps = [ @@ -2236,6 +2290,7 @@ def test_distb_empty(self): with self.assertRaises(RuntimeError): dis.distb() + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_distb_last_traceback(self): self.maxDiff = None # We need to have an existing last traceback in `sys`: @@ -2244,6 +2299,7 @@ def test_distb_last_traceback(self): self.do_disassembly_compare(self.get_disassembly(None), dis_traceback) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_distb_explicit_arg(self): self.maxDiff = None tb = get_tb() @@ -2317,6 +2373,7 @@ def check_output(self, source, expect, *flags): expect = self.text_normalize(expect) self.assertListEqual(res.splitlines(), expect.splitlines()) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_invocation(self): # test various combinations of parameters base_flags = [ @@ -2341,6 +2398,7 @@ def f(): with contextlib.redirect_stderr(io.StringIO()): _ = self.invoke_dis('--unknown') + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_show_cache(self): # test 'python -m dis -C/--show-caches' source = 'print()' @@ -2359,6 +2417,7 @@ def test_show_cache(self): for flag in ['-C', '--show-caches']: self.check_output(source, expect, flag) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_show_offsets(self): # test 'python -m dis -O/--show-offsets' source = 'pass' From aa02f02acac177b63c26c6859d6463022c40bafe Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Wed, 17 Sep 2025 10:54:51 +0200 Subject: [PATCH 10/16] Unpatch `support/__init__.py` --- Lib/test/support/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 652a8cd92b..88369e25c1 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -7,7 +7,7 @@ import dataclasses import functools import logging -# import _opcode # TODO: RUSTPYTHON +import _opcode import os import re import stat From f629c7fe1d76096e1b3741f0bd60ca2a6b6a227e Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:55:54 +0200 Subject: [PATCH 11/16] clippy --- stdlib/src/opcode.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/stdlib/src/opcode.rs b/stdlib/src/opcode.rs index 7eee6d107c..c355b59df9 100644 --- a/stdlib/src/opcode.rs +++ b/stdlib/src/opcode.rs @@ -24,7 +24,6 @@ mod opcode { // https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/opcode_ids.h#L238 const HAVE_ARGUMENT: i32 = 44; - #[must_use] pub fn try_from_pyint(raw: PyIntRef, vm: &VirtualMachine) -> PyResult { let instruction = raw .try_to_primitive::(vm) @@ -103,7 +102,7 @@ mod opcode { #[must_use] pub const fn has_exc(opcode: i32) -> bool { - Self::is_valid(opcode) && matches!(opcode, 264 | 265 | 266) + Self::is_valid(opcode) && matches!(opcode, 264..=266) } } @@ -132,7 +131,7 @@ mod opcode { ))); } v.downcast_ref::() - .ok_or_else(|| return vm.new_type_error(""))? + .ok_or_else(|| vm.new_type_error(""))? .try_to_primitive::(vm) }) .unwrap_or(Ok(0))?; @@ -144,9 +143,7 @@ mod opcode { b @ PyBool => Ok(b.is(&vm.ctx.true_value)), _n @ PyNone => Ok(false), _ => { - return Err( - vm.new_value_error("stack_effect: jump must be False, True or None") - ); + Err(vm.new_value_error("stack_effect: jump must be False, True or None")) } }) }) From 56a0edb20345a5e515c9c9ec8c4c3bd8ee180f6a Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 01:58:35 +0200 Subject: [PATCH 12/16] Make comments to doc --- compiler/core/src/bytecode.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index cbe2673d07..1088dd9848 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -198,27 +198,27 @@ pub struct CodeObject { pub instructions: Box<[CodeUnit]>, pub locations: Box<[SourceLocation]>, pub flags: CodeFlags, + /// Number of positional-only arguments pub posonlyarg_count: u32, - // Number of positional-only arguments pub arg_count: u32, pub kwonlyarg_count: u32, pub source_path: C::Name, pub first_line_number: Option, pub max_stackdepth: u32, + /// Name of the object that created this code object pub obj_name: C::Name, - // Name of the object that created this code object + /// Qualified name of the object (like CPython's co_qualname) pub qualname: C::Name, - // Qualified name of the object (like CPython's co_qualname) pub cell2arg: Option>, pub constants: Box<[C]>, pub names: Box<[C::Name]>, pub varnames: Box<[C::Name]>, pub cellvars: Box<[C::Name]>, pub freevars: Box<[C::Name]>, + /// Line number table (CPython 3.11+ format) pub linetable: Box<[u8]>, - // Line number table (CPython 3.11+ format) + /// Exception handling table pub exceptiontable: Box<[u8]>, - // Exception handling table } bitflags! { From 6b3c578c1a7eab19cb8f7e8ae0d4974eb295bde5 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 09:23:06 +0200 Subject: [PATCH 13/16] impl `_varname_from_oparg` for code --- vm/src/builtins/code.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vm/src/builtins/code.rs b/vm/src/builtins/code.rs index b49f76caa0..79ad896aaf 100644 --- a/vm/src/builtins/code.rs +++ b/vm/src/builtins/code.rs @@ -837,6 +837,15 @@ impl PyCode { }, }) } + + #[pymethod] + fn _varname_from_oparg(&self, opcode: i32, vm: &VirtualMachine) -> PyResult { + let idx_err = |vm: &VirtualMachine| vm.new_index_error("tuple index out of range"); + + let idx = usize::try_from(opcode).map_err(|_| idx_err(vm))?; + let name = self.code.varnames.get(idx).ok_or_else(|| idx_err(vm))?; + Ok(name.to_object()) + } } impl fmt::Display for PyCode { From 2634c5f2499768652d72c11feadf9cbf9d499bb3 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 09:24:09 +0200 Subject: [PATCH 14/16] Unmark passing tests --- Lib/test/test_dis.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index f9645d428e..60c8e721c2 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -2041,7 +2041,6 @@ def f(opcode, oparg, offset, *init_args): def get_instructions(self, code): return dis._get_instructions_bytes(code) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_start_offset(self): # When no extended args are present, # start_offset should be equal to offset @@ -2136,7 +2135,6 @@ def test_instantiation(self): self.assertRaises(TypeError, dis.Bytecode, object()) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_iteration(self): for obj in [_f, _C(1).__init__, "a=1", _f.__code__]: with self.subTest(obj=obj): From 98abe59e055ca64104a2b35efdc4eeea12d0b57b Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:15:40 +0200 Subject: [PATCH 15/16] Revert changes to `dis` --- Lib/dis.py | 1079 +----------------- Lib/test/test_dis.py | 2464 +----------------------------------------- 2 files changed, 57 insertions(+), 3486 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 797e0f8a08..53c85555bc 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -1,1075 +1,18 @@ -"""Disassembler of Python byte code into mnemonics.""" +from _dis import * -import sys -import types -import collections -import io -from opcode import * -from opcode import ( - __all__ as _opcodes_all, - _cache_format, - _inline_cache_entries, - _nb_ops, - _intrinsic_1_descs, - _intrinsic_2_descs, - _specializations, - _specialized_opmap, -) - -from _opcode import get_executor - -__all__ = ["code_info", "dis", "disassemble", "distb", "disco", - "findlinestarts", "findlabels", "show_code", - "get_instructions", "Instruction", "Bytecode"] + _opcodes_all -del _opcodes_all - -_have_code = (types.MethodType, types.FunctionType, types.CodeType, - classmethod, staticmethod, type) - -CONVERT_VALUE = opmap['CONVERT_VALUE'] - -SET_FUNCTION_ATTRIBUTE = opmap['SET_FUNCTION_ATTRIBUTE'] -FUNCTION_ATTR_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure') - -ENTER_EXECUTOR = opmap['ENTER_EXECUTOR'] -LOAD_CONST = opmap['LOAD_CONST'] -RETURN_CONST = opmap['RETURN_CONST'] -LOAD_GLOBAL = opmap['LOAD_GLOBAL'] -BINARY_OP = opmap['BINARY_OP'] -JUMP_BACKWARD = opmap['JUMP_BACKWARD'] -FOR_ITER = opmap['FOR_ITER'] -SEND = opmap['SEND'] -LOAD_ATTR = opmap['LOAD_ATTR'] -LOAD_SUPER_ATTR = opmap['LOAD_SUPER_ATTR'] -CALL_INTRINSIC_1 = opmap['CALL_INTRINSIC_1'] -CALL_INTRINSIC_2 = opmap['CALL_INTRINSIC_2'] -LOAD_FAST_LOAD_FAST = opmap['LOAD_FAST_LOAD_FAST'] -STORE_FAST_LOAD_FAST = opmap['STORE_FAST_LOAD_FAST'] -STORE_FAST_STORE_FAST = opmap['STORE_FAST_STORE_FAST'] - -CACHE = opmap["CACHE"] - -_all_opname = list(opname) -_all_opmap = dict(opmap) -for name, op in _specialized_opmap.items(): - # fill opname and opmap - assert op < len(_all_opname) - _all_opname[op] = name - _all_opmap[name] = op - -deoptmap = { - specialized: base for base, family in _specializations.items() for specialized in family -} - -def _try_compile(source, name): - """Attempts to compile the given source, first as an expression and - then as a statement if the first approach fails. - - Utility function to accept strings in functions that otherwise - expect code objects - """ - try: - return compile(source, name, 'eval') - except SyntaxError: - pass - return compile(source, name, 'exec') - -def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, - show_offsets=False): - """Disassemble classes, methods, functions, and other compiled objects. - - With no argument, disassemble the last traceback. - - Compiled objects currently include generator objects, async generator - objects, and coroutine objects, all of which store their code object - in a special attribute. - """ - if x is None: - distb(file=file, show_caches=show_caches, adaptive=adaptive, - show_offsets=show_offsets) - return - # Extract functions from methods. - if hasattr(x, '__func__'): - x = x.__func__ - # Extract compiled code objects from... - if hasattr(x, '__code__'): # ...a function, or - x = x.__code__ - elif hasattr(x, 'gi_code'): #...a generator object, or - x = x.gi_code - elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or - x = x.ag_code - elif hasattr(x, 'cr_code'): #...a coroutine. - x = x.cr_code - # Perform the disassembly. - if hasattr(x, '__dict__'): # Class or module - items = sorted(x.__dict__.items()) - for name, x1 in items: - if isinstance(x1, _have_code): - print("Disassembly of %s:" % name, file=file) - try: - dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) - except TypeError as msg: - print("Sorry:", msg, file=file) - print(file=file) - elif hasattr(x, 'co_code'): # Code object - _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) - elif isinstance(x, (bytes, bytearray)): # Raw bytecode - labels_map = _make_labels_map(x) - label_width = 4 + len(str(len(labels_map))) - formatter = Formatter(file=file, - offset_width=len(str(max(len(x) - 2, 9999))) if show_offsets else 0, - label_width=label_width, - show_caches=show_caches) - arg_resolver = ArgResolver(labels_map=labels_map) - _disassemble_bytes(x, arg_resolver=arg_resolver, formatter=formatter) - elif isinstance(x, str): # Source code - _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) - else: - raise TypeError("don't know how to disassemble %s objects" % - type(x).__name__) - -def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets=False): - """Disassemble a traceback (default: last traceback).""" - if tb is None: - try: - if hasattr(sys, 'last_exc'): - tb = sys.last_exc.__traceback__ - else: - tb = sys.last_traceback - except AttributeError: - raise RuntimeError("no last traceback to disassemble") from None - while tb.tb_next: tb = tb.tb_next - disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) - -# The inspect module interrogates this dictionary to build its -# list of CO_* constants. It is also used by pretty_flags to -# turn the co_flags field into a human readable list. -COMPILER_FLAG_NAMES = { - 1: "OPTIMIZED", - 2: "NEWLOCALS", - 4: "VARARGS", - 8: "VARKEYWORDS", - 16: "NESTED", - 32: "GENERATOR", - 64: "NOFREE", - 128: "COROUTINE", - 256: "ITERABLE_COROUTINE", - 512: "ASYNC_GENERATOR", -} - -def pretty_flags(flags): - """Return pretty representation of code flags.""" - names = [] - for i in range(32): - flag = 1<" - -# Sentinel to represent values that cannot be calculated -UNKNOWN = _Unknown() - -def _get_code_object(x): - """Helper to handle methods, compiled or raw code objects, and strings.""" - # Extract functions from methods. - if hasattr(x, '__func__'): - x = x.__func__ - # Extract compiled code objects from... - if hasattr(x, '__code__'): # ...a function, or - x = x.__code__ - elif hasattr(x, 'gi_code'): #...a generator object, or - x = x.gi_code - elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or - x = x.ag_code - elif hasattr(x, 'cr_code'): #...a coroutine. - x = x.cr_code - # Handle source code. - if isinstance(x, str): - x = _try_compile(x, "") - # By now, if we don't have a code object, we can't disassemble x. - if hasattr(x, 'co_code'): - return x - raise TypeError("don't know how to disassemble %s objects" % - type(x).__name__) - -def _deoptop(op): - name = _all_opname[op] - return _all_opmap[deoptmap[name]] if name in deoptmap else op - -def _get_code_array(co, adaptive): - if adaptive: - code = co._co_code_adaptive - res = [] - found = False - for i in range(0, len(code), 2): - op, arg = code[i], code[i+1] - if op == ENTER_EXECUTOR: - try: - ex = get_executor(co, i) - except (ValueError, RuntimeError): - ex = None - - if ex: - op, arg = ex.get_opcode(), ex.get_oparg() - found = True - - res.append(op.to_bytes()) - res.append(arg.to_bytes()) - return code if not found else b''.join(res) - else: - return co.co_code - -def code_info(x): - """Formatted details of methods, functions, or code.""" - return _format_code_info(_get_code_object(x)) - -def _format_code_info(co): - lines = [] - lines.append("Name: %s" % co.co_name) - lines.append("Filename: %s" % co.co_filename) - lines.append("Argument count: %s" % co.co_argcount) - lines.append("Positional-only arguments: %s" % co.co_posonlyargcount) - lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount) - lines.append("Number of locals: %s" % co.co_nlocals) - lines.append("Stack size: %s" % co.co_stacksize) - lines.append("Flags: %s" % pretty_flags(co.co_flags)) - if co.co_consts: - lines.append("Constants:") - for i_c in enumerate(co.co_consts): - lines.append("%4d: %r" % i_c) - if co.co_names: - lines.append("Names:") - for i_n in enumerate(co.co_names): - lines.append("%4d: %s" % i_n) - if co.co_varnames: - lines.append("Variable names:") - for i_n in enumerate(co.co_varnames): - lines.append("%4d: %s" % i_n) - if co.co_freevars: - lines.append("Free variables:") - for i_n in enumerate(co.co_freevars): - lines.append("%4d: %s" % i_n) - if co.co_cellvars: - lines.append("Cell variables:") - for i_n in enumerate(co.co_cellvars): - lines.append("%4d: %s" % i_n) - return "\n".join(lines) - -def show_code(co, *, file=None): - """Print details of methods, functions, or code to *file*. - - If *file* is not provided, the output is printed on stdout. - """ - print(code_info(co), file=file) - -Positions = collections.namedtuple( - 'Positions', - [ - 'lineno', - 'end_lineno', - 'col_offset', - 'end_col_offset', - ], - defaults=[None] * 4 -) - -_Instruction = collections.namedtuple( - "_Instruction", - [ - 'opname', - 'opcode', - 'arg', - 'argval', - 'argrepr', - 'offset', - 'start_offset', - 'starts_line', - 'line_number', - 'label', - 'positions', - 'cache_info', - ], - defaults=[None, None, None] -) - -_Instruction.opname.__doc__ = "Human readable name for operation" -_Instruction.opcode.__doc__ = "Numeric code for operation" -_Instruction.arg.__doc__ = "Numeric argument to operation (if any), otherwise None" -_Instruction.argval.__doc__ = "Resolved arg value (if known), otherwise same as arg" -_Instruction.argrepr.__doc__ = "Human readable description of operation argument" -_Instruction.offset.__doc__ = "Start index of operation within bytecode sequence" -_Instruction.start_offset.__doc__ = ( - "Start index of operation within bytecode sequence, including extended args if present; " - "otherwise equal to Instruction.offset" -) -_Instruction.starts_line.__doc__ = "True if this opcode starts a source line, otherwise False" -_Instruction.line_number.__doc__ = "source line number associated with this opcode (if any), otherwise None" -_Instruction.label.__doc__ = "A label (int > 0) if this instruction is a jump target, otherwise None" -_Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction" -_Instruction.cache_info.__doc__ = "list of (name, size, data), one for each cache entry of the instruction" - -_ExceptionTableEntryBase = collections.namedtuple("_ExceptionTableEntryBase", - "start end target depth lasti") - -class _ExceptionTableEntry(_ExceptionTableEntryBase): - pass - -_OPNAME_WIDTH = 20 -_OPARG_WIDTH = 5 - -def _get_cache_size(opname): - return _inline_cache_entries.get(opname, 0) - -def _get_jump_target(op, arg, offset): - """Gets the bytecode offset of the jump target if this is a jump instruction. - - Otherwise return None. - """ - deop = _deoptop(op) - caches = _get_cache_size(_all_opname[deop]) - if deop in hasjrel: - if _is_backward_jump(deop): - arg = -arg - target = offset + 2 + arg*2 - target += 2 * caches - elif deop in hasjabs: - target = arg*2 - else: - target = None - return target - -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 - start_offset - start index of operation within bytecode sequence including extended args if present; - otherwise equal to Instruction.offset - starts_line - True if this opcode starts a source line, otherwise False - line_number - source line number associated with this opcode (if any), otherwise None - label - A label if this instruction is a jump target, otherwise None - positions - Optional dis.Positions object holding the span of source code - covered by this instruction - cache_info - information about the format and content of the instruction's cache - entries (if any) - """ - - @property - def oparg(self): - """Alias for Instruction.arg.""" - return self.arg - - @property - def baseopcode(self): - """Numeric code for the base operation if operation is specialized. - - Otherwise equal to Instruction.opcode. - """ - return _deoptop(self.opcode) - - @property - def baseopname(self): - """Human readable name for the base operation if operation is specialized. - - Otherwise equal to Instruction.opname. - """ - return opname[self.baseopcode] - - @property - def cache_offset(self): - """Start index of the cache entries following the operation.""" - return self.offset + 2 - - @property - def end_offset(self): - """End index of the cache entries following the operation.""" - return self.cache_offset + _get_cache_size(_all_opname[self.opcode])*2 - - @property - def jump_target(self): - """Bytecode index of the jump target if this is a jump operation. - - Otherwise return None. - """ - return _get_jump_target(self.opcode, self.arg, self.offset) - - @property - def is_jump_target(self): - """True if other code jumps to here, otherwise False""" - return self.label is not None - - def __str__(self): - output = io.StringIO() - formatter = Formatter(file=output) - formatter.print_instruction(self, False) - return output.getvalue() - - -class Formatter: - - def __init__(self, file=None, lineno_width=0, offset_width=0, label_width=0, - line_offset=0, show_caches=False): - """Create a Formatter - - *file* where to write the output - *lineno_width* sets the width of the line number field (0 omits it) - *offset_width* sets the width of the instruction offset field - *label_width* sets the width of the label field - *show_caches* is a boolean indicating whether to display cache lines - - """ - self.file = file - self.lineno_width = lineno_width - self.offset_width = offset_width - self.label_width = label_width - self.show_caches = show_caches - - def print_instruction(self, instr, mark_as_current=False): - self.print_instruction_line(instr, mark_as_current) - if self.show_caches and instr.cache_info: - offset = instr.offset - for name, size, data in instr.cache_info: - for i in range(size): - offset += 2 - # Only show the fancy argrepr for a CACHE instruction when it's - # the first entry for a particular cache value: - if i == 0: - argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" - else: - argrepr = "" - self.print_instruction_line( - Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, - False, None, None, instr.positions), - False) - - def print_instruction_line(self, instr, mark_as_current): - """Format instruction details for inclusion in disassembly output.""" - lineno_width = self.lineno_width - offset_width = self.offset_width - label_width = self.label_width - - new_source_line = (lineno_width > 0 and - instr.starts_line and - instr.offset > 0) - if new_source_line: - print(file=self.file) - - fields = [] - # Column: Source code line number - if lineno_width: - if instr.starts_line: - lineno_fmt = "%%%dd" if instr.line_number is not None else "%%%ds" - lineno_fmt = lineno_fmt % lineno_width - lineno = _NO_LINENO if instr.line_number is None else instr.line_number - fields.append(lineno_fmt % lineno) - else: - fields.append(' ' * lineno_width) - # Column: Label - if instr.label is not None: - lbl = f"L{instr.label}:" - fields.append(f"{lbl:>{label_width}}") - else: - fields.append(' ' * label_width) - # Column: Instruction offset from start of code sequence - if offset_width > 0: - fields.append(f"{repr(instr.offset):>{offset_width}} ") - # Column: Current instruction indicator - if mark_as_current: - fields.append('-->') - else: - fields.append(' ') - # Column: Opcode name - fields.append(instr.opname.ljust(_OPNAME_WIDTH)) - # Column: Opcode argument - if instr.arg is not None: - arg = repr(instr.arg) - # If opname is longer than _OPNAME_WIDTH, we allow it to overflow into - # the space reserved for oparg. This results in fewer misaligned opargs - # in the disassembly output. - opname_excess = max(0, len(instr.opname) - _OPNAME_WIDTH) - fields.append(repr(instr.arg).rjust(_OPARG_WIDTH - opname_excess)) - # Column: Opcode argument details - if instr.argrepr: - fields.append('(' + instr.argrepr + ')') - print(' '.join(fields).rstrip(), file=self.file) - - def print_exception_table(self, exception_entries): - file = self.file - if exception_entries: - print("ExceptionTable:", file=file) - for entry in exception_entries: - lasti = " lasti" if entry.lasti else "" - start = entry.start_label - end = entry.end_label - target = entry.target_label - print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file) - - -class ArgResolver: - def __init__(self, co_consts=None, names=None, varname_from_oparg=None, labels_map=None): - self.co_consts = co_consts - self.names = names - self.varname_from_oparg = varname_from_oparg - self.labels_map = labels_map or {} - - def offset_from_jump_arg(self, op, arg, offset): - deop = _deoptop(op) - if deop in hasjabs: - return arg * 2 - elif deop in hasjrel: - signed_arg = -arg if _is_backward_jump(deop) else arg - argval = offset + 2 + signed_arg*2 - caches = _get_cache_size(_all_opname[deop]) - argval += 2 * caches - return argval - return None - - def get_label_for_offset(self, offset): - return self.labels_map.get(offset, None) - - def get_argval_argrepr(self, op, arg, offset): - get_name = None if self.names is None else self.names.__getitem__ - argval = None - argrepr = '' - deop = _deoptop(op) - if arg is not None: - # Set argval to the dereferenced value of the argument when - # available, and argrepr to the string representation of argval. - # _disassemble_bytes needs the string repr of the - # raw name index for LOAD_GLOBAL, LOAD_CONST, etc. - argval = arg - if deop in hasconst: - argval, argrepr = _get_const_info(deop, arg, self.co_consts) - elif deop in hasname: - if deop == LOAD_GLOBAL: - argval, argrepr = _get_name_info(arg//2, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL" - elif deop == LOAD_ATTR: - argval, argrepr = _get_name_info(arg//2, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL|self" - elif deop == LOAD_SUPER_ATTR: - argval, argrepr = _get_name_info(arg//4, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL|self" - else: - argval, argrepr = _get_name_info(arg, get_name) - elif deop in hasjump or deop in hasexc: - argval = self.offset_from_jump_arg(op, arg, offset) - lbl = self.get_label_for_offset(argval) - assert lbl is not None - argrepr = f"to L{lbl}" - elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST): - arg1 = arg >> 4 - arg2 = arg & 15 - val1, argrepr1 = _get_name_info(arg1, self.varname_from_oparg) - val2, argrepr2 = _get_name_info(arg2, self.varname_from_oparg) - argrepr = argrepr1 + ", " + argrepr2 - argval = val1, val2 - elif deop in haslocal or deop in hasfree: - argval, argrepr = _get_name_info(arg, self.varname_from_oparg) - elif deop in hascompare: - argval = cmp_op[arg >> 5] - argrepr = argval - if arg & 16: - argrepr = f"bool({argrepr})" - elif deop == CONVERT_VALUE: - argval = (None, str, repr, ascii)[arg] - argrepr = ('', 'str', 'repr', 'ascii')[arg] - elif deop == SET_FUNCTION_ATTRIBUTE: - argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS) - if arg & (1<> 1 - lasti = bool(dl&1) - entries.append(_ExceptionTableEntry(start, end, target, depth, lasti)) - except StopIteration: - return entries - -def _is_backward_jump(op): - return opname[op] in ('JUMP_BACKWARD', - 'JUMP_BACKWARD_NO_INTERRUPT') - -def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=None, - original_code=None, arg_resolver=None): - """Iterate over the instructions in a bytecode string. - - Generates a sequence of Instruction namedtuples giving the details of each - opcode. - - """ - # Use the basic, unadaptive code for finding labels and actually walking the - # bytecode, since replacements like ENTER_EXECUTOR and INSTRUMENTED_* can - # mess that logic up pretty badly: - original_code = original_code or code - co_positions = co_positions or iter(()) - - starts_line = False - local_line_number = None - line_number = None - for offset, start_offset, op, arg in _unpack_opargs(original_code): - if linestarts is not None: - starts_line = offset in linestarts - if starts_line: - local_line_number = linestarts[offset] - if local_line_number is not None: - line_number = local_line_number + line_offset - else: - line_number = None - positions = Positions(*next(co_positions, ())) - deop = _deoptop(op) - op = code[offset] - - if arg_resolver: - argval, argrepr = arg_resolver.get_argval_argrepr(op, arg, offset) - else: - argval, argrepr = arg, repr(arg) - - caches = _get_cache_size(_all_opname[deop]) - # Advance the co_positions iterator: - for _ in range(caches): - next(co_positions, ()) - - if caches: - cache_info = [] - for name, size in _cache_format[opname[deop]].items(): - data = code[offset + 2: offset + 2 + 2 * size] - cache_info.append((name, size, data)) - else: - cache_info = None - - label = arg_resolver.get_label_for_offset(offset) if arg_resolver else None - yield Instruction(_all_opname[op], op, arg, argval, argrepr, - offset, start_offset, starts_line, line_number, - label, positions, cache_info) - - -def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, - show_offsets=False): - """Disassemble a code object.""" - linestarts = dict(findlinestarts(co)) - exception_entries = _parse_exception_table(co) - labels_map = _make_labels_map(co.co_code, exception_entries=exception_entries) - label_width = 4 + len(str(len(labels_map))) - formatter = Formatter(file=file, - lineno_width=_get_lineno_width(linestarts), - offset_width=len(str(max(len(co.co_code) - 2, 9999))) if show_offsets else 0, - label_width=label_width, - show_caches=show_caches) - arg_resolver = ArgResolver(co_consts=co.co_consts, - names=co.co_names, - varname_from_oparg=co._varname_from_oparg, - labels_map=labels_map) - _disassemble_bytes(_get_code_array(co, adaptive), lasti, linestarts, - exception_entries=exception_entries, co_positions=co.co_positions(), - original_code=co.co_code, arg_resolver=arg_resolver, formatter=formatter) - -def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False): - disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) - if depth is None or depth > 0: - if depth is not None: - depth = depth - 1 - for x in co.co_consts: - if hasattr(x, 'co_code'): - print(file=file) - print("Disassembly of %r:" % (x,), file=file) - _disassemble_recursive( - x, file=file, depth=depth, show_caches=show_caches, - adaptive=adaptive, show_offsets=show_offsets - ) - - -def _make_labels_map(original_code, exception_entries=()): - jump_targets = set(findlabels(original_code)) - labels = set(jump_targets) - for start, end, target, _, _ in exception_entries: - labels.add(start) - labels.add(end) - labels.add(target) - labels = sorted(labels) - labels_map = {offset: i+1 for (i, offset) in enumerate(sorted(labels))} - for e in exception_entries: - e.start_label = labels_map[e.start] - e.end_label = labels_map[e.end] - e.target_label = labels_map[e.target] - return labels_map - -_NO_LINENO = ' --' - -def _get_lineno_width(linestarts): - if linestarts is None: - return 0 - maxlineno = max(filter(None, linestarts.values()), default=-1) - if maxlineno == -1: - # Omit the line number column entirely if we have no line number info - return 0 - lineno_width = max(3, len(str(maxlineno))) - if lineno_width < len(_NO_LINENO) and None in linestarts.values(): - lineno_width = len(_NO_LINENO) - return lineno_width - - -def _disassemble_bytes(code, lasti=-1, linestarts=None, - *, line_offset=0, exception_entries=(), - co_positions=None, original_code=None, - arg_resolver=None, formatter=None): - - assert formatter is not None - assert arg_resolver is not None - - instrs = _get_instructions_bytes(code, linestarts=linestarts, - line_offset=line_offset, - co_positions=co_positions, - original_code=original_code, - arg_resolver=arg_resolver) - - print_instructions(instrs, exception_entries, formatter, lasti=lasti) - - -def print_instructions(instrs, exception_entries, formatter, lasti=-1): - for instr in instrs: - # Each CACHE takes 2 bytes - is_current_instr = instr.offset <= lasti \ - <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) - formatter.print_instruction(instr, is_current_instr) - - formatter.print_exception_table(exception_entries) - -def _disassemble_str(source, **kwargs): - """Compile the source string, then disassemble the code object.""" - _disassemble_recursive(_try_compile(source, ''), **kwargs) - -disco = disassemble # XXX For backwards compatibility - - -# Rely on C `int` being 32 bits for oparg -_INT_BITS = 32 -# Value for c int when it overflows -_INT_OVERFLOW = 2 ** (_INT_BITS - 1) - -def _unpack_opargs(code): - extended_arg = 0 - extended_args_offset = 0 # Number of EXTENDED_ARG instructions preceding the current instruction - caches = 0 - for i in range(0, len(code), 2): - # Skip inline CACHE entries: - if caches: - caches -= 1 - continue - op = code[i] - deop = _deoptop(op) - caches = _get_cache_size(_all_opname[deop]) - if deop in hasarg: - arg = code[i+1] | extended_arg - extended_arg = (arg << 8) if deop == EXTENDED_ARG else 0 - # The oparg is stored as a signed integer - # If the value exceeds its upper limit, it will overflow and wrap - # to a negative integer - if extended_arg >= _INT_OVERFLOW: - extended_arg -= 2 * _INT_OVERFLOW - else: - arg = None - extended_arg = 0 - if deop == EXTENDED_ARG: - extended_args_offset += 1 - yield (i, i, op, arg) - else: - start_offset = i - extended_args_offset*2 - yield (i, start_offset, op, arg) - extended_args_offset = 0 - -def findlabels(code): - """Detect all offsets in a byte code which are jump targets. - - Return the list of offsets. - - """ - labels = [] - for offset, _, op, arg in _unpack_opargs(code): - if arg is not None: - label = _get_jump_target(op, arg, offset) - if label is None: - continue - if label not in labels: - labels.append(label) - return labels - -def findlinestarts(code): - """Find the offsets in a byte code which are start of lines in the source. - - Generate pairs (offset, lineno) - lineno will be an integer or None the offset does not have a source line. - """ - - lastline = False # None is a valid line number - for start, end, line in code.co_lines(): - if line is not lastline: - lastline = line - yield start, line - return - -def _find_imports(co): - """Find import statements in the code - - Generate triplets (name, level, fromlist) where - name is the imported module and level, fromlist are - the corresponding args to __import__. - """ - IMPORT_NAME = opmap['IMPORT_NAME'] - - consts = co.co_consts - names = co.co_names - opargs = [(op, arg) for _, _, op, arg in _unpack_opargs(co.co_code) - if op != EXTENDED_ARG] - for i, (op, oparg) in enumerate(opargs): - if op == IMPORT_NAME and i >= 2: - from_op = opargs[i-1] - level_op = opargs[i-2] - if (from_op[0] in hasconst and level_op[0] in hasconst): - level = _get_const_value(level_op[0], level_op[1], consts) - fromlist = _get_const_value(from_op[0], from_op[1], consts) - yield (names[oparg], level, fromlist) - -def _find_store_names(co): - """Find names of variables which are written in the code - - Generate sequence of strings - """ - STORE_OPS = { - opmap['STORE_NAME'], - opmap['STORE_GLOBAL'] - } - - names = co.co_names - for _, _, op, arg in _unpack_opargs(co.co_code): - if op in STORE_OPS: - yield names[arg] - - -class Bytecode: - """The bytecode operations of a piece of code - - Instantiate this with a function, method, other compiled object, 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, show_caches=False, adaptive=False, show_offsets=False): - self.codeobj = co = _get_code_object(x) - if first_line is None: - self.first_line = co.co_firstlineno - self._line_offset = 0 - else: - self.first_line = first_line - self._line_offset = first_line - co.co_firstlineno - self._linestarts = dict(findlinestarts(co)) - self._original_object = x - self.current_offset = current_offset - self.exception_entries = _parse_exception_table(co) - self.show_caches = show_caches - self.adaptive = adaptive - self.show_offsets = show_offsets - - def __iter__(self): - co = self.codeobj - original_code = co.co_code - labels_map = _make_labels_map(original_code, self.exception_entries) - arg_resolver = ArgResolver(co_consts=co.co_consts, - names=co.co_names, - varname_from_oparg=co._varname_from_oparg, - labels_map=labels_map) - return _get_instructions_bytes(_get_code_array(co, self.adaptive), - linestarts=self._linestarts, - line_offset=self._line_offset, - co_positions=co.co_positions(), - original_code=original_code, - arg_resolver=arg_resolver) - - def __repr__(self): - return "{}({!r})".format(self.__class__.__name__, - self._original_object) - - @classmethod - def from_traceback(cls, tb, *, show_caches=False, adaptive=False): - """ Construct a Bytecode from the given traceback """ - while tb.tb_next: - tb = tb.tb_next - return cls( - tb.tb_frame.f_code, current_offset=tb.tb_lasti, show_caches=show_caches, adaptive=adaptive - ) - - def info(self): - """Return formatted information about the code object.""" - return _format_code_info(self.codeobj) - - def dis(self): - """Return a formatted view of the bytecode operations.""" - co = self.codeobj - if self.current_offset is not None: - offset = self.current_offset - else: - offset = -1 - with io.StringIO() as output: - code = _get_code_array(co, self.adaptive) - offset_width = len(str(max(len(code) - 2, 9999))) if self.show_offsets else 0 - - - labels_map = _make_labels_map(co.co_code, self.exception_entries) - label_width = 4 + len(str(len(labels_map))) - formatter = Formatter(file=output, - lineno_width=_get_lineno_width(self._linestarts), - offset_width=offset_width, - label_width=label_width, - line_offset=self._line_offset, - show_caches=self.show_caches) - - arg_resolver = ArgResolver(co_consts=co.co_consts, - names=co.co_names, - varname_from_oparg=co._varname_from_oparg, - labels_map=labels_map) - _disassemble_bytes(code, - linestarts=self._linestarts, - line_offset=self._line_offset, - lasti=offset, - exception_entries=self.exception_entries, - co_positions=co.co_positions(), - original_code=co.co_code, - arg_resolver=arg_resolver, - formatter=formatter) - return output.getvalue() - - -def main(args=None): +# Disassembling a file by following cpython Lib/dis.py +def _test(): + """Simple test program to disassemble a file.""" import argparse parser = argparse.ArgumentParser() - parser.add_argument('-C', '--show-caches', action='store_true', - help='show inline caches') - parser.add_argument('-O', '--show-offsets', action='store_true', - help='show instruction offsets') - parser.add_argument('infile', nargs='?', default='-') - args = parser.parse_args(args=args) - if args.infile == '-': - name = '' - source = sys.stdin.buffer.read() - else: - name = args.infile - with open(args.infile, 'rb') as infile: - source = infile.read() - code = compile(source, name, "exec") - dis(code, show_caches=args.show_caches, show_offsets=args.show_offsets) + parser.add_argument('infile', type=argparse.FileType('rb'), nargs='?', default='-') + args = parser.parse_args() + with args.infile as infile: + source = infile.read() + code = compile(source, args.infile.name, "exec") + dis(code) if __name__ == "__main__": - main() + _test() diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 60c8e721c2..8bbba86a46 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1,2431 +1,59 @@ -# Minimal tests for dis module - -import contextlib -import dis -import functools -import io -import itertools -import opcode -import re +import subprocess import sys -import tempfile -import textwrap -import types import unittest -from test.support import (captured_stdout, requires_debug_ranges, - requires_specialization, cpython_only, - os_helper) -from test.support.bytecode_helper import BytecodeTestCase - - -CACHE = dis.opmap["CACHE"] - -def get_tb(): - def _error(): - try: - 1 / 0 - except Exception as e: - tb = e.__traceback__ - return tb - - tb = _error() - while tb.tb_next: - tb = tb.tb_next - return tb -TRACEBACK_CODE = get_tb().tb_frame.f_code +# This only tests that it prints something in order +# to avoid changing this test if the bytecode changes -class _C: - def __init__(self, x): - self.x = x == 1 +# These tests start a new process instead of redirecting stdout because +# stdout is being written to by rust code, which currently can't be +# redirected by reassigning sys.stdout - @staticmethod - def sm(x): - x = x == 1 +class TestDis(unittest.TestCase): @classmethod - def cm(cls, x): - cls.x = x == 1 - -dis_c_instance_method = """\ -%3d RESUME 0 - -%3d LOAD_FAST 1 (x) - LOAD_CONST 1 (1) - COMPARE_OP 72 (==) - LOAD_FAST 0 (self) - STORE_ATTR 0 (x) - RETURN_CONST 0 (None) -""" % (_C.__init__.__code__.co_firstlineno, _C.__init__.__code__.co_firstlineno + 1,) - -dis_c_instance_method_bytes = """\ - RESUME 0 - LOAD_FAST 1 - LOAD_CONST 1 - COMPARE_OP 72 (==) - LOAD_FAST 0 - STORE_ATTR 0 - RETURN_CONST 0 -""" - -dis_c_class_method = """\ -%3d RESUME 0 - -%3d LOAD_FAST 1 (x) - LOAD_CONST 1 (1) - COMPARE_OP 72 (==) - LOAD_FAST 0 (cls) - STORE_ATTR 0 (x) - RETURN_CONST 0 (None) -""" % (_C.cm.__code__.co_firstlineno, _C.cm.__code__.co_firstlineno + 2,) - -dis_c_static_method = """\ -%3d RESUME 0 - -%3d LOAD_FAST 0 (x) - LOAD_CONST 1 (1) - COMPARE_OP 72 (==) - STORE_FAST 0 (x) - RETURN_CONST 0 (None) -""" % (_C.sm.__code__.co_firstlineno, _C.sm.__code__.co_firstlineno + 2,) - -# Class disassembling info has an extra newline at end. -dis_c = """\ -Disassembly of %s: -%s -Disassembly of %s: -%s -Disassembly of %s: -%s -""" % (_C.__init__.__name__, dis_c_instance_method, - _C.cm.__name__, dis_c_class_method, - _C.sm.__name__, dis_c_static_method) - -def _f(a): - print(a) - return 1 - -dis_f = """\ -%3d RESUME 0 - -%3d LOAD_GLOBAL 1 (print + NULL) - LOAD_FAST 0 (a) - CALL 1 - POP_TOP - -%3d RETURN_CONST 1 (1) -""" % (_f.__code__.co_firstlineno, - _f.__code__.co_firstlineno + 1, - _f.__code__.co_firstlineno + 2) - -dis_f_with_offsets = """\ -%3d 0 RESUME 0 - -%3d 2 LOAD_GLOBAL 1 (print + NULL) - 12 LOAD_FAST 0 (a) - 14 CALL 1 - 22 POP_TOP - -%3d 24 RETURN_CONST 1 (1) -""" % (_f.__code__.co_firstlineno, - _f.__code__.co_firstlineno + 1, - _f.__code__.co_firstlineno + 2) - - -dis_f_co_code = """\ - RESUME 0 - LOAD_GLOBAL 1 - LOAD_FAST 0 - CALL 1 - POP_TOP - RETURN_CONST 1 -""" - -def bug708901(): - for res in range(1, - 10): - pass - -dis_bug708901 = """\ -%3d RESUME 0 - -%3d LOAD_GLOBAL 1 (range + NULL) - LOAD_CONST 1 (1) - -%3d LOAD_CONST 2 (10) - -%3d CALL 2 - GET_ITER - L1: FOR_ITER 3 (to L2) - STORE_FAST 0 (res) - -%3d JUMP_BACKWARD 5 (to L1) - -%3d L2: END_FOR - POP_TOP - RETURN_CONST 0 (None) -""" % (bug708901.__code__.co_firstlineno, - bug708901.__code__.co_firstlineno + 1, - bug708901.__code__.co_firstlineno + 2, - bug708901.__code__.co_firstlineno + 1, - bug708901.__code__.co_firstlineno + 3, - bug708901.__code__.co_firstlineno + 1) - - -def bug1333982(x=[]): - assert 0, ((s for s in x) + - 1) - pass - -dis_bug1333982 = """\ -%3d RESUME 0 - -%3d LOAD_ASSERTION_ERROR - LOAD_CONST 1 ( at 0x..., file "%s", line %d>) - MAKE_FUNCTION - LOAD_FAST 0 (x) - GET_ITER - CALL 0 - -%3d LOAD_CONST 2 (1) - -%3d BINARY_OP 0 (+) - CALL 0 - RAISE_VARARGS 1 -""" % (bug1333982.__code__.co_firstlineno, - bug1333982.__code__.co_firstlineno + 1, - __file__, - bug1333982.__code__.co_firstlineno + 1, - bug1333982.__code__.co_firstlineno + 2, - bug1333982.__code__.co_firstlineno + 1) - - -def bug42562(): - pass - - -# Set line number for 'pass' to None -# TODO: RUSTPYTHON; AttributeError: attribute '__code__' of 'function' objects is not writable -# bug42562.__code__ = bug42562.__code__.replace(co_linetable=b'\xf8') - - -dis_bug42562 = """\ - RESUME 0 - RETURN_CONST 0 (None) -""" - -# Extended arg followed by NOP -code_bug_45757 = bytes([ - opcode.opmap['EXTENDED_ARG'], 0x01, # EXTENDED_ARG 0x01 - opcode.opmap['NOP'], 0xFF, # NOP 0xFF - opcode.opmap['EXTENDED_ARG'], 0x01, # EXTENDED_ARG 0x01 - opcode.opmap['LOAD_CONST'], 0x29, # LOAD_CONST 0x29 - opcode.opmap['RETURN_VALUE'], 0x00, # RETURN_VALUE 0x00 - ]) - -dis_bug_45757 = """\ - EXTENDED_ARG 1 - NOP - EXTENDED_ARG 1 - LOAD_CONST 297 - RETURN_VALUE -""" - -# [255, 255, 255, 252] is -4 in a 4 byte signed integer -bug46724 = bytes([ - opcode.EXTENDED_ARG, 255, - opcode.EXTENDED_ARG, 255, - opcode.EXTENDED_ARG, 255, - opcode.opmap['JUMP_FORWARD'], 252, -]) - - -dis_bug46724 = """\ - L1: EXTENDED_ARG 255 - EXTENDED_ARG 65535 - EXTENDED_ARG 16777215 - JUMP_FORWARD -4 (to L1) -""" - -def func_w_kwargs(a, b, **c): - pass - -def wrap_func_w_kwargs(): - func_w_kwargs(1, 2, c=5) - -dis_kw_names = """\ -%3d RESUME 0 - -%3d LOAD_GLOBAL 1 (func_w_kwargs + NULL) - LOAD_CONST 1 (1) - LOAD_CONST 2 (2) - LOAD_CONST 3 (5) - LOAD_CONST 4 (('c',)) - CALL_KW 3 - POP_TOP - RETURN_CONST 0 (None) -""" % (wrap_func_w_kwargs.__code__.co_firstlineno, - wrap_func_w_kwargs.__code__.co_firstlineno + 1) - -dis_intrinsic_1_2 = """\ - 0 RESUME 0 - - 1 LOAD_CONST 0 (0) - LOAD_CONST 1 (('*',)) - IMPORT_NAME 0 (math) - CALL_INTRINSIC_1 2 (INTRINSIC_IMPORT_STAR) - POP_TOP - RETURN_CONST 2 (None) -""" - -dis_intrinsic_1_5 = """\ - 0 RESUME 0 - - 1 LOAD_NAME 0 (a) - CALL_INTRINSIC_1 5 (INTRINSIC_UNARY_POSITIVE) - RETURN_VALUE -""" - -dis_intrinsic_1_6 = """\ - 0 RESUME 0 - - 1 BUILD_LIST 0 - LOAD_NAME 0 (a) - LIST_EXTEND 1 - CALL_INTRINSIC_1 6 (INTRINSIC_LIST_TO_TUPLE) - RETURN_VALUE -""" - -_BIG_LINENO_FORMAT = """\ - 1 RESUME 0 - -%3d LOAD_GLOBAL 0 (spam) - POP_TOP - RETURN_CONST 0 (None) -""" - -_BIG_LINENO_FORMAT2 = """\ - 1 RESUME 0 - -%4d LOAD_GLOBAL 0 (spam) - POP_TOP - RETURN_CONST 0 (None) -""" - -dis_module_expected_results = """\ -Disassembly of f: - 4 RESUME 0 - RETURN_CONST 0 (None) - -Disassembly of g: - 5 RESUME 0 - RETURN_CONST 0 (None) - -""" - -expr_str = "x + 1" - -dis_expr_str = """\ - 0 RESUME 0 - - 1 LOAD_NAME 0 (x) - LOAD_CONST 0 (1) - BINARY_OP 0 (+) - RETURN_VALUE -""" - -simple_stmt_str = "x = x + 1" - -dis_simple_stmt_str = """\ - 0 RESUME 0 - - 1 LOAD_NAME 0 (x) - LOAD_CONST 0 (1) - BINARY_OP 0 (+) - STORE_NAME 0 (x) - RETURN_CONST 1 (None) -""" - -annot_stmt_str = """\ - -x: int = 1 -y: fun(1) -lst[fun(0)]: int = 1 -""" -# leading newline is for a reason (tests lineno) - -dis_annot_stmt_str = """\ - 0 RESUME 0 - - 2 SETUP_ANNOTATIONS - LOAD_CONST 0 (1) - STORE_NAME 0 (x) - LOAD_NAME 1 (int) - LOAD_NAME 2 (__annotations__) - LOAD_CONST 1 ('x') - STORE_SUBSCR - - 3 LOAD_NAME 3 (fun) - PUSH_NULL - LOAD_CONST 0 (1) - CALL 1 - LOAD_NAME 2 (__annotations__) - LOAD_CONST 2 ('y') - STORE_SUBSCR - - 4 LOAD_CONST 0 (1) - LOAD_NAME 4 (lst) - LOAD_NAME 3 (fun) - PUSH_NULL - LOAD_CONST 3 (0) - CALL 1 - STORE_SUBSCR - LOAD_NAME 1 (int) - POP_TOP - RETURN_CONST 4 (None) -""" - -compound_stmt_str = """\ -x = 0 -while 1: - x += 1""" -# Trailing newline has been deliberately omitted - -dis_compound_stmt_str = """\ - 0 RESUME 0 - - 1 LOAD_CONST 0 (0) - STORE_NAME 0 (x) - - 2 NOP - - 3 L1: LOAD_NAME 0 (x) - LOAD_CONST 1 (1) - BINARY_OP 13 (+=) - STORE_NAME 0 (x) - - 2 JUMP_BACKWARD 7 (to L1) + def setUpClass(cls): + cls.setup = """ +import dis +def tested_func(): pass """ + cls.command = (sys.executable, "-c") -dis_traceback = """\ -%4d RESUME 0 - -%4d NOP - -%4d L1: LOAD_CONST 1 (1) - LOAD_CONST 2 (0) - --> BINARY_OP 11 (/) - POP_TOP - -%4d L2: LOAD_FAST_CHECK 1 (tb) - RETURN_VALUE - - -- L3: PUSH_EXC_INFO - -%4d LOAD_GLOBAL 0 (Exception) - CHECK_EXC_MATCH - POP_JUMP_IF_FALSE 23 (to L7) - STORE_FAST 0 (e) - -%4d L4: LOAD_FAST 0 (e) - LOAD_ATTR 2 (__traceback__) - STORE_FAST 1 (tb) - L5: POP_EXCEPT - LOAD_CONST 0 (None) - STORE_FAST 0 (e) - DELETE_FAST 0 (e) - -%4d LOAD_FAST 1 (tb) - RETURN_VALUE - - -- L6: LOAD_CONST 0 (None) - STORE_FAST 0 (e) - DELETE_FAST 0 (e) - RERAISE 1 - -%4d L7: RERAISE 0 - - -- L8: COPY 3 - POP_EXCEPT - RERAISE 1 -ExceptionTable: - L1 to L2 -> L3 [0] - L3 to L4 -> L8 [1] lasti - L4 to L5 -> L6 [1] lasti - L6 to L8 -> L8 [1] lasti -""" % (TRACEBACK_CODE.co_firstlineno, - TRACEBACK_CODE.co_firstlineno + 1, - TRACEBACK_CODE.co_firstlineno + 2, - TRACEBACK_CODE.co_firstlineno + 5, - TRACEBACK_CODE.co_firstlineno + 3, - TRACEBACK_CODE.co_firstlineno + 4, - TRACEBACK_CODE.co_firstlineno + 5, - TRACEBACK_CODE.co_firstlineno + 3) - -def _fstring(a, b, c, d): - return f'{a} {b:4} {c!r} {d!r:4}' - -dis_fstring = """\ -%3d RESUME 0 - -%3d LOAD_FAST 0 (a) - FORMAT_SIMPLE - LOAD_CONST 1 (' ') - LOAD_FAST 1 (b) - LOAD_CONST 2 ('4') - FORMAT_WITH_SPEC - LOAD_CONST 1 (' ') - LOAD_FAST 2 (c) - CONVERT_VALUE 2 (repr) - FORMAT_SIMPLE - LOAD_CONST 1 (' ') - LOAD_FAST 3 (d) - CONVERT_VALUE 2 (repr) - LOAD_CONST 2 ('4') - FORMAT_WITH_SPEC - BUILD_STRING 7 - RETURN_VALUE -""" % (_fstring.__code__.co_firstlineno, _fstring.__code__.co_firstlineno + 1) - -def _with(c): - with c: - x = 1 - y = 2 - -dis_with = """\ -%4d RESUME 0 - -%4d LOAD_FAST 0 (c) - BEFORE_WITH - L1: POP_TOP - -%4d LOAD_CONST 1 (1) - STORE_FAST 1 (x) - -%4d L2: LOAD_CONST 0 (None) - LOAD_CONST 0 (None) - LOAD_CONST 0 (None) - CALL 2 - POP_TOP - -%4d LOAD_CONST 2 (2) - STORE_FAST 2 (y) - RETURN_CONST 0 (None) - -%4d L3: PUSH_EXC_INFO - WITH_EXCEPT_START - TO_BOOL - POP_JUMP_IF_TRUE 1 (to L4) - RERAISE 2 - L4: POP_TOP - L5: POP_EXCEPT - POP_TOP - POP_TOP - -%4d LOAD_CONST 2 (2) - STORE_FAST 2 (y) - RETURN_CONST 0 (None) - - -- L6: COPY 3 - POP_EXCEPT - RERAISE 1 -ExceptionTable: - L1 to L2 -> L3 [1] lasti - L3 to L5 -> L6 [3] lasti -""" % (_with.__code__.co_firstlineno, - _with.__code__.co_firstlineno + 1, - _with.__code__.co_firstlineno + 2, - _with.__code__.co_firstlineno + 1, - _with.__code__.co_firstlineno + 3, - _with.__code__.co_firstlineno + 1, - _with.__code__.co_firstlineno + 3, - ) - -async def _asyncwith(c): - async with c: - x = 1 - y = 2 - -dis_asyncwith = """\ -%4d RETURN_GENERATOR - POP_TOP - L1: RESUME 0 - -%4d LOAD_FAST 0 (c) - BEFORE_ASYNC_WITH - GET_AWAITABLE 1 - LOAD_CONST 0 (None) - L2: SEND 3 (to L5) - L3: YIELD_VALUE 1 - L4: RESUME 3 - JUMP_BACKWARD_NO_INTERRUPT 5 (to L2) - L5: END_SEND - L6: POP_TOP - -%4d LOAD_CONST 1 (1) - STORE_FAST 1 (x) - -%4d L7: LOAD_CONST 0 (None) - LOAD_CONST 0 (None) - LOAD_CONST 0 (None) - CALL 2 - GET_AWAITABLE 2 - LOAD_CONST 0 (None) - L8: SEND 3 (to L11) - L9: YIELD_VALUE 1 - L10: RESUME 3 - JUMP_BACKWARD_NO_INTERRUPT 5 (to L8) - L11: END_SEND - POP_TOP - -%4d LOAD_CONST 2 (2) - STORE_FAST 2 (y) - RETURN_CONST 0 (None) - -%4d L12: CLEANUP_THROW - L13: JUMP_BACKWARD_NO_INTERRUPT 25 (to L5) - L14: CLEANUP_THROW - L15: JUMP_BACKWARD_NO_INTERRUPT 9 (to L11) - L16: PUSH_EXC_INFO - WITH_EXCEPT_START - GET_AWAITABLE 2 - LOAD_CONST 0 (None) - L17: SEND 4 (to L21) - L18: YIELD_VALUE 1 - L19: RESUME 3 - JUMP_BACKWARD_NO_INTERRUPT 5 (to L17) - L20: CLEANUP_THROW - L21: END_SEND - TO_BOOL - POP_JUMP_IF_TRUE 1 (to L22) - RERAISE 2 - L22: POP_TOP - L23: POP_EXCEPT - POP_TOP - POP_TOP - -%4d LOAD_CONST 2 (2) - STORE_FAST 2 (y) - RETURN_CONST 0 (None) - - -- L24: COPY 3 - POP_EXCEPT - RERAISE 1 - L25: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) - RERAISE 1 -ExceptionTable: - L1 to L3 -> L25 [0] lasti - L3 to L4 -> L12 [3] - L4 to L6 -> L25 [0] lasti - L6 to L7 -> L16 [1] lasti - L7 to L9 -> L25 [0] lasti - L9 to L10 -> L14 [2] - L10 to L13 -> L25 [0] lasti - L14 to L15 -> L25 [0] lasti - L16 to L18 -> L24 [3] lasti - L18 to L19 -> L20 [6] - L19 to L23 -> L24 [3] lasti - L23 to L25 -> L25 [0] lasti -""" % (_asyncwith.__code__.co_firstlineno, - _asyncwith.__code__.co_firstlineno + 1, - _asyncwith.__code__.co_firstlineno + 2, - _asyncwith.__code__.co_firstlineno + 1, - _asyncwith.__code__.co_firstlineno + 3, - _asyncwith.__code__.co_firstlineno + 1, - _asyncwith.__code__.co_firstlineno + 3, - ) - - -def _tryfinally(a, b): - try: - return a - finally: - b() - -def _tryfinallyconst(b): - try: - return 1 - finally: - b() - -dis_tryfinally = """\ -%4d RESUME 0 - -%4d NOP - -%4d L1: LOAD_FAST 0 (a) - -%4d L2: LOAD_FAST 1 (b) - PUSH_NULL - CALL 0 - POP_TOP - RETURN_VALUE - - -- L3: PUSH_EXC_INFO - -%4d LOAD_FAST 1 (b) - PUSH_NULL - CALL 0 - POP_TOP - RERAISE 0 - - -- L4: COPY 3 - POP_EXCEPT - RERAISE 1 -ExceptionTable: - L1 to L2 -> L3 [0] - L3 to L4 -> L4 [1] lasti -""" % (_tryfinally.__code__.co_firstlineno, - _tryfinally.__code__.co_firstlineno + 1, - _tryfinally.__code__.co_firstlineno + 2, - _tryfinally.__code__.co_firstlineno + 4, - _tryfinally.__code__.co_firstlineno + 4, - ) - -dis_tryfinallyconst = """\ -%4d RESUME 0 - -%4d NOP - -%4d NOP - -%4d LOAD_FAST 0 (b) - PUSH_NULL - CALL 0 - POP_TOP - RETURN_CONST 1 (1) - - -- L1: PUSH_EXC_INFO - -%4d LOAD_FAST 0 (b) - PUSH_NULL - CALL 0 - POP_TOP - RERAISE 0 - - -- L2: COPY 3 - POP_EXCEPT - RERAISE 1 -ExceptionTable: - L1 to L2 -> L2 [1] lasti -""" % (_tryfinallyconst.__code__.co_firstlineno, - _tryfinallyconst.__code__.co_firstlineno + 1, - _tryfinallyconst.__code__.co_firstlineno + 2, - _tryfinallyconst.__code__.co_firstlineno + 4, - _tryfinallyconst.__code__.co_firstlineno + 4, - ) - -def _g(x): - yield x - -async def _ag(x): - yield x - -async def _co(x): - async for item in _ag(x): - pass - -def _h(y): - def foo(x): - '''funcdoc''' - return list(x + z for z in y) - return foo - -dis_nested_0 = """\ - -- MAKE_CELL 0 (y) - -%4d RESUME 0 - -%4d LOAD_FAST 0 (y) - BUILD_TUPLE 1 - LOAD_CONST 1 () - MAKE_FUNCTION - SET_FUNCTION_ATTRIBUTE 8 (closure) - STORE_FAST 1 (foo) - -%4d LOAD_FAST 1 (foo) - RETURN_VALUE -""" % (_h.__code__.co_firstlineno, - _h.__code__.co_firstlineno + 1, - __file__, - _h.__code__.co_firstlineno + 1, - _h.__code__.co_firstlineno + 4, -) - -dis_nested_1 = """%s -Disassembly of : - -- COPY_FREE_VARS 1 - MAKE_CELL 0 (x) - -%4d RESUME 0 - -%4d LOAD_GLOBAL 1 (list + NULL) - LOAD_FAST 0 (x) - BUILD_TUPLE 1 - LOAD_CONST 1 ( at 0x..., file "%s", line %d>) - MAKE_FUNCTION - SET_FUNCTION_ATTRIBUTE 8 (closure) - LOAD_DEREF 1 (y) - GET_ITER - CALL 0 - CALL 1 - RETURN_VALUE -""" % (dis_nested_0, - __file__, - _h.__code__.co_firstlineno + 1, - _h.__code__.co_firstlineno + 1, - _h.__code__.co_firstlineno + 3, - __file__, - _h.__code__.co_firstlineno + 3, -) - -dis_nested_2 = """%s -Disassembly of at 0x..., file "%s", line %d>: - -- COPY_FREE_VARS 1 - -%4d RETURN_GENERATOR - POP_TOP - L1: RESUME 0 - LOAD_FAST 0 (.0) - GET_ITER - L2: FOR_ITER 10 (to L3) - STORE_FAST 1 (z) - LOAD_DEREF 2 (x) - LOAD_FAST 1 (z) - BINARY_OP 0 (+) - YIELD_VALUE 0 - RESUME 5 - POP_TOP - JUMP_BACKWARD 12 (to L2) - L3: END_FOR - POP_TOP - RETURN_CONST 0 (None) - - -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) - RERAISE 1 -ExceptionTable: - L1 to L4 -> L4 [0] lasti -""" % (dis_nested_1, - __file__, - _h.__code__.co_firstlineno + 3, - _h.__code__.co_firstlineno + 3, -) - -def load_test(x, y=0): - a, b = x, y - return a, b - -dis_load_test_quickened_code = """\ -%3d RESUME_CHECK 0 - -%3d LOAD_FAST_LOAD_FAST 1 (x, y) - STORE_FAST_STORE_FAST 50 (b, a) - -%3d LOAD_FAST_LOAD_FAST 35 (a, b) - BUILD_TUPLE 2 - RETURN_VALUE -""" % (load_test.__code__.co_firstlineno, - load_test.__code__.co_firstlineno + 1, - load_test.__code__.co_firstlineno + 2) - -def loop_test(): - for i in [1, 2, 3] * 3: - load_test(i) - -dis_loop_test_quickened_code = """\ -%3d RESUME_CHECK 0 - -%3d BUILD_LIST 0 - LOAD_CONST 1 ((1, 2, 3)) - LIST_EXTEND 1 - LOAD_CONST 2 (3) - BINARY_OP 5 (*) - GET_ITER - L1: FOR_ITER_LIST 14 (to L2) - STORE_FAST 0 (i) - -%3d LOAD_GLOBAL_MODULE 1 (load_test + NULL) - LOAD_FAST 0 (i) - CALL_PY_GENERAL 1 - POP_TOP - JUMP_BACKWARD 16 (to L1) - -%3d L2: END_FOR - POP_TOP - RETURN_CONST 0 (None) -""" % (loop_test.__code__.co_firstlineno, - loop_test.__code__.co_firstlineno + 1, - loop_test.__code__.co_firstlineno + 2, - loop_test.__code__.co_firstlineno + 1,) - -def extended_arg_quick(): - *_, _ = ... - -dis_extended_arg_quick_code = """\ -%3d RESUME 0 - -%3d LOAD_CONST 1 (Ellipsis) - EXTENDED_ARG 1 - UNPACK_EX 256 - POP_TOP - STORE_FAST 0 (_) - RETURN_CONST 0 (None) -"""% (extended_arg_quick.__code__.co_firstlineno, - extended_arg_quick.__code__.co_firstlineno + 1,) - -ADAPTIVE_WARMUP_DELAY = 2 - -class DisTestBase(unittest.TestCase): - "Common utilities for DisTests and TestDisTraceback" - - def strip_addresses(self, text): - return re.sub(r'\b0x[0-9A-Fa-f]+\b', '0x...', text) - - def assert_exception_table_increasing(self, lines): - prev_start, prev_end = -1, -1 - count = 0 - for line in lines: - m = re.match(r' L(\d+) to L(\d+) -> L\d+ \[\d+\]', line) - start, end = [int(g) for g in m.groups()] - self.assertGreaterEqual(end, start) - self.assertGreaterEqual(start, prev_end) - prev_start, prev_end = start, end - count += 1 - return count - - def do_disassembly_compare(self, got, expected): - if got != expected: - got = self.strip_addresses(got) - self.assertEqual(got, expected) - - -class DisTests(DisTestBase): - - maxDiff = None - - def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): - # We want to test the default printing behaviour, not the file arg - output = io.StringIO() - with contextlib.redirect_stdout(output): - if wrapper: - dis.dis(func, **kwargs) - else: - dis.disassemble(func, lasti, **kwargs) - return output.getvalue() - - def get_disassemble_as_string(self, func, lasti=-1): - return self.get_disassembly(func, lasti, False) - - def do_disassembly_test(self, func, expected, **kwargs): - self.maxDiff = None - got = self.get_disassembly(func, depth=0, **kwargs) - self.do_disassembly_compare(got, expected) - # Add checks for dis.disco - if hasattr(func, '__code__'): - got_disco = io.StringIO() - with contextlib.redirect_stdout(got_disco): - dis.disco(func.__code__, **kwargs) - self.do_disassembly_compare(got_disco.getvalue(), expected) - - def test_opmap(self): - self.assertEqual(dis.opmap["CACHE"], 0) - self.assertIn(dis.opmap["LOAD_CONST"], dis.hasconst) - self.assertIn(dis.opmap["STORE_NAME"], dis.hasname) - - def test_opname(self): - self.assertEqual(dis.opname[dis.opmap["LOAD_FAST"]], "LOAD_FAST") - - def test_boundaries(self): - self.assertEqual(dis.opmap["EXTENDED_ARG"], dis.EXTENDED_ARG) - - def test_widths(self): - long_opcodes = set(['JUMP_BACKWARD_NO_INTERRUPT', - 'INSTRUMENTED_CALL_FUNCTION_EX']) - for opcode, opname in enumerate(dis.opname): - if opname in long_opcodes or opname.startswith("INSTRUMENTED"): - continue - with self.subTest(opname=opname): - width = dis._OPNAME_WIDTH - if opcode in dis.hasarg: - width += 1 + dis._OPARG_WIDTH - self.assertLessEqual(len(opname), width) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' def test_dis(self): - self.do_disassembly_test(_f, dis_f) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_dis_with_offsets(self): - self.do_disassembly_test(_f, dis_f_with_offsets, show_offsets=True) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_bug_708901(self): - self.do_disassembly_test(bug708901, dis_bug708901) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_bug_1333982(self): - # This one is checking bytecodes generated for an `assert` statement, - # so fails if the tests are run with -O. Skip this test then. - if not __debug__: - self.skipTest('need asserts, run without -O') - - self.do_disassembly_test(bug1333982, dis_bug1333982) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_bug_42562(self): - self.do_disassembly_test(bug42562, dis_bug42562) - - def test_bug_45757(self): - # Extended arg followed by NOP - self.do_disassembly_test(code_bug_45757, dis_bug_45757) - - def test_bug_46724(self): - # Test that negative operargs are handled properly - self.do_disassembly_test(bug46724, dis_bug46724) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_kw_names(self): - # Test that value is displayed for keyword argument names: - self.do_disassembly_test(wrap_func_w_kwargs, dis_kw_names) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_intrinsic_1(self): - # Test that argrepr is displayed for CALL_INTRINSIC_1 - self.do_disassembly_test("from math import *", dis_intrinsic_1_2) - self.do_disassembly_test("+a", dis_intrinsic_1_5) - self.do_disassembly_test("(*a,)", dis_intrinsic_1_6) - - @unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: rustPython does not implement this feature yet - def test_intrinsic_2(self): - self.assertIn("CALL_INTRINSIC_2 1 (INTRINSIC_PREP_RERAISE_STAR)", - self.get_disassembly("try: pass\nexcept* Exception: x")) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_big_linenos(self): - def func(count): - namespace = {} - func = "def foo():\n " + "".join(["\n "] * count + ["spam\n"]) - exec(func, namespace) - return namespace['foo'] - - # Test all small ranges - for i in range(1, 300): - expected = _BIG_LINENO_FORMAT % (i + 2) - self.do_disassembly_test(func(i), expected) - - # Test some larger ranges too - for i in range(300, 1000, 10): - expected = _BIG_LINENO_FORMAT % (i + 2) - self.do_disassembly_test(func(i), expected) - - for i in range(1000, 5000, 10): - expected = _BIG_LINENO_FORMAT2 % (i + 2) - self.do_disassembly_test(func(i), expected) - - from test import dis_module - self.do_disassembly_test(dis_module, dis_module_expected_results) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_str(self): - self.do_disassembly_test(expr_str, dis_expr_str) - self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str) - self.do_disassembly_test(annot_stmt_str, dis_annot_stmt_str) - self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_disassemble_bytes(self): - self.do_disassembly_test(_f.__code__.co_code, dis_f_co_code) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_class(self): - self.do_disassembly_test(_C, dis_c) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_instance_method(self): - self.do_disassembly_test(_C(1).__init__, dis_c_instance_method) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_disassemble_instance_method_bytes(self): - method_bytecode = _C(1).__init__.__code__.co_code - self.do_disassembly_test(method_bytecode, dis_c_instance_method_bytes) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_static_method(self): - self.do_disassembly_test(_C.sm, dis_c_static_method) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_class_method(self): - self.do_disassembly_test(_C.cm, dis_c_class_method) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_generator(self): - gen_func_disas = self.get_disassembly(_g) # Generator function - gen_disas = self.get_disassembly(_g(1)) # Generator iterator - self.assertEqual(gen_disas, gen_func_disas) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_async_generator(self): - agen_func_disas = self.get_disassembly(_ag) # Async generator function - agen_disas = self.get_disassembly(_ag(1)) # Async generator iterator - self.assertEqual(agen_disas, agen_func_disas) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_coroutine(self): - coro_func_disas = self.get_disassembly(_co) # Coroutine function - coro = _co(1) # Coroutine object - coro.close() # Avoid a RuntimeWarning (never awaited) - coro_disas = self.get_disassembly(coro) - self.assertEqual(coro_disas, coro_func_disas) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_fstring(self): - self.do_disassembly_test(_fstring, dis_fstring) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_with(self): - self.do_disassembly_test(_with, dis_with) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_asyncwith(self): - self.do_disassembly_test(_asyncwith, dis_asyncwith) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_try_finally(self): - self.do_disassembly_test(_tryfinally, dis_tryfinally) - self.do_disassembly_test(_tryfinallyconst, dis_tryfinallyconst) - - def test_dis_none(self): - try: - del sys.last_exc - except AttributeError: - pass - try: - del sys.last_traceback - except AttributeError: - pass - self.assertRaises(RuntimeError, dis.dis, None) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_dis_traceback(self): - self.maxDiff = None - try: - del sys.last_traceback - except AttributeError: - pass - - try: - 1/0 - except Exception as e: - tb = e.__traceback__ - sys.last_exc = e - - tb_dis = self.get_disassemble_as_string(tb.tb_frame.f_code, tb.tb_lasti) - self.do_disassembly_test(None, tb_dis) - - def test_dis_object(self): - self.assertRaises(TypeError, dis.dis, object()) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassemble_recursive(self): - def check(expected, **kwargs): - dis = self.get_disassembly(_h, **kwargs) - dis = self.strip_addresses(dis) - self.assertEqual(dis, expected) - - check(dis_nested_0, depth=0) - check(dis_nested_1, depth=1) - check(dis_nested_2, depth=2) - check(dis_nested_2, depth=3) - check(dis_nested_2, depth=None) - check(dis_nested_2) - - def test__try_compile_no_context_exc_on_error(self): - # see gh-102114 - try: - dis._try_compile(")", "") - except Exception as e: - self.assertIsNone(e.__context__) - - @staticmethod - def code_quicken(f, times=ADAPTIVE_WARMUP_DELAY): - for _ in range(times): - f() - - @cpython_only - @requires_specialization - def test_super_instructions(self): - self.code_quicken(lambda: load_test(0, 0)) - got = self.get_disassembly(load_test, adaptive=True) - self.do_disassembly_compare(got, dis_load_test_quickened_code) - - @cpython_only - @requires_specialization - def test_binary_specialize(self): - binary_op_quicken = """\ - 0 RESUME_CHECK 0 - - 1 LOAD_NAME 0 (a) - LOAD_NAME 1 (b) - %s - RETURN_VALUE -""" - co_int = compile('a + b', "", "eval") - self.code_quicken(lambda: exec(co_int, {}, {'a': 1, 'b': 2})) - got = self.get_disassembly(co_int, adaptive=True) - self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_INT 0 (+)") - - co_unicode = compile('a + b', "", "eval") - self.code_quicken(lambda: exec(co_unicode, {}, {'a': 'a', 'b': 'b'})) - got = self.get_disassembly(co_unicode, adaptive=True) - self.do_disassembly_compare(got, binary_op_quicken % "BINARY_OP_ADD_UNICODE 0 (+)") - - binary_subscr_quicken = """\ - 0 RESUME_CHECK 0 - - 1 LOAD_NAME 0 (a) - LOAD_CONST 0 (0) - %s - RETURN_VALUE -""" - co_list = compile('a[0]', "", "eval") - self.code_quicken(lambda: exec(co_list, {}, {'a': [0]})) - got = self.get_disassembly(co_list, adaptive=True) - self.do_disassembly_compare(got, binary_subscr_quicken % "BINARY_SUBSCR_LIST_INT") - - co_dict = compile('a[0]', "", "eval") - self.code_quicken(lambda: exec(co_dict, {}, {'a': {0: '1'}})) - got = self.get_disassembly(co_dict, adaptive=True) - self.do_disassembly_compare(got, binary_subscr_quicken % "BINARY_SUBSCR_DICT") - - @cpython_only - @requires_specialization - def test_load_attr_specialize(self): - load_attr_quicken = """\ - 0 RESUME_CHECK 0 - - 1 LOAD_CONST 0 ('a') - LOAD_ATTR_SLOT 0 (__class__) - RETURN_VALUE -""" - co = compile("'a'.__class__", "", "eval") - self.code_quicken(lambda: exec(co, {}, {})) - got = self.get_disassembly(co, adaptive=True) - self.do_disassembly_compare(got, load_attr_quicken) - - @cpython_only - @requires_specialization - def test_call_specialize(self): - call_quicken = """\ - 0 RESUME_CHECK 0 - - 1 LOAD_NAME 0 (str) - PUSH_NULL - LOAD_CONST 0 (1) - CALL_STR_1 1 - RETURN_VALUE -""" - co = compile("str(1)", "", "eval") - self.code_quicken(lambda: exec(co, {}, {})) - got = self.get_disassembly(co, adaptive=True) - self.do_disassembly_compare(got, call_quicken) - - @cpython_only - @requires_specialization - def test_loop_quicken(self): - # Loop can trigger a quicken where the loop is located - self.code_quicken(loop_test, 4) - got = self.get_disassembly(loop_test, adaptive=True) - expected = dis_loop_test_quickened_code - self.do_disassembly_compare(got, expected) - - @cpython_only - def test_extended_arg_quick(self): - got = self.get_disassembly(extended_arg_quick) - self.do_disassembly_compare(got, dis_extended_arg_quick_code) - - def get_cached_values(self, quickened, adaptive): - def f(): - l = [] - for i in range(42): - l.append(i) - if quickened: - self.code_quicken(f) - else: - # "copy" the code to un-quicken it: - f.__code__ = f.__code__.replace() - for instruction in _unroll_caches_as_Instructions(dis.get_instructions( - f, show_caches=True, adaptive=adaptive - ), show_caches=True): - if instruction.opname == "CACHE": - yield instruction.argrepr - - @cpython_only - def test_show_caches(self): - for quickened in (False, True): - for adaptive in (False, True): - with self.subTest(f"{quickened=}, {adaptive=}"): - if adaptive: - pattern = r"^(\w+: \d+)?$" - else: - pattern = r"^(\w+: 0)?$" - caches = list(self.get_cached_values(quickened, adaptive)) - for cache in caches: - self.assertRegex(cache, pattern) - total_caches = 21 - empty_caches = 7 - self.assertEqual(caches.count(""), empty_caches) - self.assertEqual(len(caches), total_caches) - - @cpython_only - def test_show_currinstr_with_cache(self): - """ - Make sure that with lasti pointing to CACHE, it still shows the current - line correctly - """ - def f(): - print(a) - # The code above should generate a LOAD_GLOBAL which has CACHE instr after - # However, this might change in the future. So we explicitly try to find - # a CACHE entry in the instructions. If we can't do that, fail the test - - for inst in _unroll_caches_as_Instructions( - dis.get_instructions(f, show_caches=True), show_caches=True): - if inst.opname == "CACHE": - op_offset = inst.offset - 2 - cache_offset = inst.offset - break - else: - opname = inst.opname - else: - self.fail("Can't find a CACHE entry in the function provided to do the test") - - assem_op = self.get_disassembly(f.__code__, lasti=op_offset, wrapper=False) - assem_cache = self.get_disassembly(f.__code__, lasti=cache_offset, wrapper=False) - - # Make sure --> exists and points to the correct op - self.assertRegex(assem_op, fr"--> {opname}") - # Make sure when lasti points to cache, it shows the same disassembly - self.assertEqual(assem_op, assem_cache) - - -class DisWithFileTests(DisTests): - - # Run the tests again, using the file arg instead of print - def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): - output = io.StringIO() - if wrapper: - dis.dis(func, file=output, **kwargs) - else: - dis.disassemble(func, lasti, file=output, **kwargs) - return output.getvalue() - - -if dis.code_info.__doc__ is None: - code_info_consts = "0: None" -else: - code_info_consts = "0: 'Formatted details of methods, functions, or code.'" - -code_info_code_info = f"""\ -Name: code_info -Filename: (.*) -Argument count: 1 -Positional-only arguments: 0 -Kw-only arguments: 0 -Number of locals: 1 -Stack size: \\d+ -Flags: OPTIMIZED, NEWLOCALS -Constants: - {code_info_consts} -Names: - 0: _format_code_info - 1: _get_code_object -Variable names: - 0: x""" - - -@staticmethod -def tricky(a, b, /, x, y, z=True, *args, c, d, e=[], **kwds): - def f(c=c): - print(a, b, x, y, z, c, d, e, f) - yield a, b, x, y, z, c, d, e, f - -code_info_tricky = """\ -Name: tricky -Filename: (.*) -Argument count: 5 -Positional-only arguments: 2 -Kw-only arguments: 3 -Number of locals: 10 -Stack size: \\d+ -Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR -Constants: - 0: None - 1: -Variable names: - 0: a - 1: b - 2: x - 3: y - 4: z - 5: c - 6: d - 7: e - 8: args - 9: kwds -Cell variables: - 0: [abedfxyz] - 1: [abedfxyz] - 2: [abedfxyz] - 3: [abedfxyz] - 4: [abedfxyz] - 5: [abedfxyz]""" -# NOTE: the order of the cell variables above depends on dictionary order! - -co_tricky_nested_f = tricky.__func__.__code__.co_consts[1] - -code_info_tricky_nested_f = """\ -Filename: (.*) -Argument count: 1 -Positional-only arguments: 0 -Kw-only arguments: 0 -Number of locals: 1 -Stack size: \\d+ -Flags: OPTIMIZED, NEWLOCALS, NESTED -Constants: - 0: None -Names: - 0: print -Variable names: - 0: c -Free variables: - 0: [abedfxyz] - 1: [abedfxyz] - 2: [abedfxyz] - 3: [abedfxyz] - 4: [abedfxyz] - 5: [abedfxyz]""" - -code_info_expr_str = """\ -Name: -Filename: -Argument count: 0 -Positional-only arguments: 0 -Kw-only arguments: 0 -Number of locals: 0 -Stack size: \\d+ -Flags: 0x0 -Constants: - 0: 1 -Names: - 0: x""" - -code_info_simple_stmt_str = """\ -Name: -Filename: -Argument count: 0 -Positional-only arguments: 0 -Kw-only arguments: 0 -Number of locals: 0 -Stack size: \\d+ -Flags: 0x0 -Constants: - 0: 1 - 1: None -Names: - 0: x""" - -code_info_compound_stmt_str = """\ -Name: -Filename: -Argument count: 0 -Positional-only arguments: 0 -Kw-only arguments: 0 -Number of locals: 0 -Stack size: \\d+ -Flags: 0x0 -Constants: - 0: 0 - 1: 1 -Names: - 0: x""" - - -async def async_def(): - await 1 - async for a in b: pass - async with c as d: pass - -code_info_async_def = """\ -Name: async_def -Filename: (.*) -Argument count: 0 -Positional-only arguments: 0 -Kw-only arguments: 0 -Number of locals: 2 -Stack size: \\d+ -Flags: OPTIMIZED, NEWLOCALS, COROUTINE -Constants: - 0: None - 1: 1 -Names: - 0: b - 1: c -Variable names: - 0: a - 1: d""" - -class CodeInfoTests(unittest.TestCase): - test_pairs = [ - (dis.code_info, code_info_code_info), - (tricky, code_info_tricky), - (co_tricky_nested_f, code_info_tricky_nested_f), - (expr_str, code_info_expr_str), - (simple_stmt_str, code_info_simple_stmt_str), - (compound_stmt_str, code_info_compound_stmt_str), - (async_def, code_info_async_def) - ] - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_code_info(self): - self.maxDiff = 1000 - for x, expected in self.test_pairs: - self.assertRegex(dis.code_info(x), expected) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_show_code(self): - self.maxDiff = 1000 - for x, expected in self.test_pairs: - with captured_stdout() as output: - dis.show_code(x) - self.assertRegex(output.getvalue(), expected+"\n") - output = io.StringIO() - dis.show_code(x, file=output) - self.assertRegex(output.getvalue(), expected) - - def test_code_info_object(self): - self.assertRaises(TypeError, dis.code_info, object()) - - def test_pretty_flags_no_flags(self): - self.assertEqual(dis.pretty_flags(0), '0x0') - - -# Fodder for instruction introspection tests -# Editing any of these may require recalculating the expected output -def outer(a=1, b=2): - def f(c=3, d=4): - def inner(e=5, f=6): - print(a, b, c, d, e, f) - print(a, b, c, d) - return inner - print(a, b, '', 1, [], {}, "Hello world!") - return f - -def jumpy(): - # This won't actually run (but that's OK, we only disassemble it) - for i in range(10): - print(i) - if i < 4: - continue - if i > 6: - break - else: - print("I can haz else clause?") - while i: - print(i) - i -= 1 - if i > 6: - continue - if i < 4: - break - else: - print("Who let lolcatz into this test suite?") - try: - 1 / 0 - except ZeroDivisionError: - print("Here we go, here we go, here we go...") - else: - with i as dodgy: - print("Never reach this") - finally: - print("OK, now we're done") - -# End fodder for opinfo generation tests -expected_outer_line = 1 -_line_offset = outer.__code__.co_firstlineno - 1 -code_object_f = outer.__code__.co_consts[1] -# TODO: RUSTPYTHON; AttributeError: 'int' object has no attribute 'co_firstlineno' -# expected_f_line = code_object_f.co_firstlineno - _line_offset -# TODO: RUSTPYTHON; AttributeError: 'int' object has no attribute 'co_consts' -#code_object_inner = code_object_f.co_consts[1] -# TODO: RUSTPYTHON; NameError: name 'code_object_inner' is not defined -# expected_inner_line = code_object_inner.co_firstlineno - _line_offset -expected_jumpy_line = 1 - -# The following lines are useful to regenerate the expected results after -# either the fodder is modified or the bytecode generation changes -# After regeneration, update the references to code_object_f and -# code_object_inner before rerunning the tests - -def _stringify_instruction(instr): - # Since line numbers and other offsets change a lot for these - # test cases, ignore them. - return f" {instr._replace(positions=None)!r}," - -def _prepare_test_cases(): - ignore = io.StringIO() - with contextlib.redirect_stdout(ignore): - f = outer() - inner = f() - _instructions_outer = dis.get_instructions(outer, first_line=expected_outer_line) - _instructions_f = dis.get_instructions(f, first_line=expected_f_line) - _instructions_inner = dis.get_instructions(inner, first_line=expected_inner_line) - _instructions_jumpy = dis.get_instructions(jumpy, first_line=expected_jumpy_line) - result = "\n".join( - [ - "expected_opinfo_outer = [", - *map(_stringify_instruction, _instructions_outer), - "]", - "", - "expected_opinfo_f = [", - *map(_stringify_instruction, _instructions_f), - "]", - "", - "expected_opinfo_inner = [", - *map(_stringify_instruction, _instructions_inner), - "]", - "", - "expected_opinfo_jumpy = [", - *map(_stringify_instruction, _instructions_jumpy), - "]", - ] - ) - result = result.replace(repr(repr(code_object_f)), "repr(code_object_f)") - result = result.replace(repr(code_object_f), "code_object_f") - result = result.replace(repr(repr(code_object_inner)), "repr(code_object_inner)") - result = result.replace(repr(code_object_inner), "code_object_inner") - print(result) - -# from test.test_dis import _prepare_test_cases; _prepare_test_cases() - -Instruction = dis.Instruction - -expected_opinfo_outer = [ - Instruction(opname='MAKE_CELL', opcode=94, arg=0, argval='a', argrepr='a', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None), - Instruction(opname='MAKE_CELL', opcode=94, arg=1, argval='b', argrepr='b', offset=2, start_offset=2, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=4, start_offset=4, starts_line=True, line_number=1, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=(3, 4), argrepr='(3, 4)', offset=6, start_offset=6, starts_line=True, line_number=2, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='a', argrepr='a', offset=8, start_offset=8, starts_line=False, line_number=2, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=1, argval='b', argrepr='b', offset=10, start_offset=10, starts_line=False, line_number=2, label=None, positions=None), - Instruction(opname='BUILD_TUPLE', opcode=52, arg=2, argval=2, argrepr='', offset=12, start_offset=12, starts_line=False, line_number=2, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=code_object_f, argrepr=repr(code_object_f), offset=14, start_offset=14, starts_line=False, line_number=2, label=None, positions=None), - Instruction(opname='MAKE_FUNCTION', opcode=26, arg=None, argval=None, argrepr='', offset=16, start_offset=16, starts_line=False, line_number=2, label=None, positions=None), - Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=8, argval=8, argrepr='closure', offset=18, start_offset=18, starts_line=False, line_number=2, label=None, positions=None), - Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=1, argval=1, argrepr='defaults', offset=20, start_offset=20, starts_line=False, line_number=2, label=None, positions=None), - Instruction(opname='STORE_FAST', opcode=110, arg=2, argval='f', argrepr='f', offset=22, start_offset=22, starts_line=False, line_number=2, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='print', argrepr='print + NULL', offset=24, start_offset=24, starts_line=True, line_number=7, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=0, argval='a', argrepr='a', offset=34, start_offset=34, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=1, argval='b', argrepr='b', offset=36, start_offset=36, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval='', argrepr="''", offset=38, start_offset=38, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=1, argrepr='1', offset=40, start_offset=40, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='BUILD_LIST', opcode=47, arg=0, argval=0, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='BUILD_MAP', opcode=48, arg=0, argval=0, argrepr='', offset=44, start_offset=44, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=4, argval='Hello world!', argrepr="'Hello world!'", offset=46, start_offset=46, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=7, argval=7, argrepr='', offset=48, start_offset=48, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=56, start_offset=56, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=2, argval='f', argrepr='f', offset=58, start_offset=58, starts_line=True, line_number=8, label=None, positions=None), - Instruction(opname='RETURN_VALUE', opcode=36, arg=None, argval=None, argrepr='', offset=60, start_offset=60, starts_line=False, line_number=8, label=None, positions=None), -] - -expected_opinfo_f = [ - Instruction(opname='COPY_FREE_VARS', opcode=62, arg=2, argval=2, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None), - Instruction(opname='MAKE_CELL', opcode=94, arg=0, argval='c', argrepr='c', offset=2, start_offset=2, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='MAKE_CELL', opcode=94, arg=1, argval='d', argrepr='d', offset=4, start_offset=4, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=6, start_offset=6, starts_line=True, line_number=2, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=(5, 6), argrepr='(5, 6)', offset=8, start_offset=8, starts_line=True, line_number=3, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=3, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=4, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='c', argrepr='c', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=1, argval='d', argrepr='d', offset=16, start_offset=16, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='BUILD_TUPLE', opcode=52, arg=4, argval=4, argrepr='', offset=18, start_offset=18, starts_line=False, line_number=3, label=None, positions=None), - # TODO: RUSTPYTHON; NameError: name 'code_object_inner' is not defined - #Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, start_offset=20, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='MAKE_FUNCTION', opcode=26, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=8, argval=8, argrepr='closure', offset=24, start_offset=24, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='SET_FUNCTION_ATTRIBUTE', opcode=106, arg=1, argval=1, argrepr='defaults', offset=26, start_offset=26, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='STORE_FAST', opcode=110, arg=2, argval='inner', argrepr='inner', offset=28, start_offset=28, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=5, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=3, argval='a', argrepr='a', offset=40, start_offset=40, starts_line=False, line_number=5, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=4, argval='b', argrepr='b', offset=42, start_offset=42, starts_line=False, line_number=5, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=0, argval='c', argrepr='c', offset=44, start_offset=44, starts_line=False, line_number=5, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=1, argval='d', argrepr='d', offset=46, start_offset=46, starts_line=False, line_number=5, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=4, argval=4, argrepr='', offset=48, start_offset=48, starts_line=False, line_number=5, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=56, start_offset=56, starts_line=False, line_number=5, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=2, argval='inner', argrepr='inner', offset=58, start_offset=58, starts_line=True, line_number=6, label=None, positions=None), - Instruction(opname='RETURN_VALUE', opcode=36, arg=None, argval=None, argrepr='', offset=60, start_offset=60, starts_line=False, line_number=6, label=None, positions=None), -] - -expected_opinfo_inner = [ - Instruction(opname='COPY_FREE_VARS', opcode=62, arg=4, argval=4, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=None, label=None, positions=None), - Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=2, start_offset=2, starts_line=True, line_number=3, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='print', argrepr='print + NULL', offset=4, start_offset=4, starts_line=True, line_number=4, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=2, argval='a', argrepr='a', offset=14, start_offset=14, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=3, argval='b', argrepr='b', offset=16, start_offset=16, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=4, argval='c', argrepr='c', offset=18, start_offset=18, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='LOAD_DEREF', opcode=84, arg=5, argval='d', argrepr='d', offset=20, start_offset=20, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='LOAD_FAST_LOAD_FAST', opcode=88, arg=1, argval=('e', 'f'), argrepr='e, f', offset=22, start_offset=22, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=6, argval=6, argrepr='', offset=24, start_offset=24, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=32, start_offset=32, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=34, start_offset=34, starts_line=False, line_number=4, label=None, positions=None), -] - -expected_opinfo_jumpy = [ - Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=1, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='range', argrepr='range + NULL', offset=2, start_offset=2, starts_line=True, line_number=3, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=10, argrepr='10', offset=12, start_offset=12, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='GET_ITER', opcode=19, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), - Instruction(opname='FOR_ITER', opcode=72, arg=30, argval=88, argrepr='to L4', offset=24, start_offset=24, starts_line=False, line_number=3, label=1, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=28, start_offset=28, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=4, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=40, start_offset=40, starts_line=False, line_number=4, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=4, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=4, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=52, start_offset=52, starts_line=True, line_number=5, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=54, start_offset=54, starts_line=False, line_number=5, label=None, positions=None, cache_info=None), - Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=56, start_offset=56, starts_line=False, line_number=5, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=68, argrepr='to L2', offset=60, start_offset=60, starts_line=False, line_number=5, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='JUMP_BACKWARD', opcode=77, arg=22, argval=24, argrepr='to L1', offset=64, start_offset=64, starts_line=True, line_number=6, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=68, start_offset=68, starts_line=True, line_number=7, label=2, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=70, start_offset=70, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), - Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=72, start_offset=72, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=2, argval=84, argrepr='to L3', offset=76, start_offset=76, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='JUMP_BACKWARD', opcode=77, arg=30, argval=24, argrepr='to L1', offset=80, start_offset=80, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=84, start_offset=84, starts_line=True, line_number=8, label=3, positions=None, cache_info=None), - Instruction(opname='JUMP_FORWARD', opcode=79, arg=13, argval=114, argrepr='to L5', offset=86, start_offset=86, starts_line=False, line_number=8, label=None, positions=None, cache_info=None), - Instruction(opname='END_FOR', opcode=11, arg=None, argval=None, argrepr='', offset=88, start_offset=88, starts_line=True, line_number=3, label=4, positions=None, cache_info=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=90, start_offset=90, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=92, start_offset=92, starts_line=True, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=83, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=102, start_offset=102, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=104, start_offset=104, starts_line=False, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=112, start_offset=112, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_FAST_CHECK', opcode=87, arg=0, argval='i', argrepr='i', offset=114, start_offset=114, starts_line=True, line_number=11, label=5, positions=None, cache_info=None), - Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=116, start_offset=116, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=40, argval=208, argrepr='to L9', offset=124, start_offset=124, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=128, start_offset=128, starts_line=True, line_number=12, label=6, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=138, start_offset=138, starts_line=False, line_number=12, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=140, start_offset=140, starts_line=False, line_number=12, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=148, start_offset=148, starts_line=False, line_number=12, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=150, start_offset=150, starts_line=True, line_number=13, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=152, start_offset=152, starts_line=False, line_number=13, label=None, positions=None, cache_info=None), - Instruction(opname='BINARY_OP', opcode=45, arg=23, argval=23, argrepr='-=', offset=154, start_offset=154, starts_line=False, line_number=13, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=158, start_offset=158, starts_line=False, line_number=13, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=160, start_offset=160, starts_line=True, line_number=14, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=162, start_offset=162, starts_line=False, line_number=14, label=None, positions=None, cache_info=None), - Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=164, start_offset=164, starts_line=False, line_number=14, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=176, argrepr='to L7', offset=168, start_offset=168, starts_line=False, line_number=14, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='JUMP_BACKWARD', opcode=77, arg=31, argval=114, argrepr='to L5', offset=172, start_offset=172, starts_line=True, line_number=15, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=176, start_offset=176, starts_line=True, line_number=16, label=7, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=178, start_offset=178, starts_line=False, line_number=16, label=None, positions=None, cache_info=None), - Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=180, start_offset=180, starts_line=False, line_number=16, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=1, argval=190, argrepr='to L8', offset=184, start_offset=184, starts_line=False, line_number=16, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='JUMP_FORWARD', opcode=79, arg=20, argval=230, argrepr='to L10', offset=188, start_offset=188, starts_line=True, line_number=17, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=190, start_offset=190, starts_line=True, line_number=11, label=8, positions=None, cache_info=None), - Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=192, start_offset=192, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=208, argrepr='to L9', offset=200, start_offset=200, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='JUMP_BACKWARD', opcode=77, arg=40, argval=128, argrepr='to L6', offset=204, start_offset=204, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=208, start_offset=208, starts_line=True, line_number=19, label=9, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=83, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=218, start_offset=218, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=220, start_offset=220, starts_line=False, line_number=19, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=228, start_offset=228, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), - Instruction(opname='NOP', opcode=30, arg=None, argval=None, argrepr='', offset=230, start_offset=230, starts_line=True, line_number=20, label=10, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=232, start_offset=232, starts_line=True, line_number=21, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=7, argval=0, argrepr='0', offset=234, start_offset=234, starts_line=False, line_number=21, label=None, positions=None, cache_info=None), - Instruction(opname='BINARY_OP', opcode=45, arg=11, argval=11, argrepr='/', offset=236, start_offset=236, starts_line=False, line_number=21, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=240, start_offset=240, starts_line=False, line_number=21, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=242, start_offset=242, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='BEFORE_WITH', opcode=2, arg=None, argval=None, argrepr='', offset=244, start_offset=244, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='STORE_FAST', opcode=110, arg=1, argval='dodgy', argrepr='dodgy', offset=246, start_offset=246, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=248, start_offset=248, starts_line=True, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=83, arg=8, argval='Never reach this', argrepr="'Never reach this'", offset=258, start_offset=258, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=260, start_offset=260, starts_line=False, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=268, start_offset=268, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=270, start_offset=270, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=272, start_offset=272, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=274, start_offset=274, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=2, argval=2, argrepr='', offset=276, start_offset=276, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=284, start_offset=284, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=286, start_offset=286, starts_line=True, line_number=28, label=11, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=296, start_offset=296, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=298, start_offset=298, starts_line=False, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=306, start_offset=306, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), - Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=308, start_offset=308, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), - Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=310, start_offset=310, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='WITH_EXCEPT_START', opcode=44, arg=None, argval=None, argrepr='', offset=312, start_offset=312, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=314, start_offset=314, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=1, argval=328, argrepr='to L12', offset=322, start_offset=322, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='RERAISE', opcode=102, arg=2, argval=2, argrepr='', offset=326, start_offset=326, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=328, start_offset=328, starts_line=False, line_number=25, label=12, positions=None, cache_info=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=330, start_offset=330, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=332, start_offset=332, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=334, start_offset=334, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=26, argval=286, argrepr='to L11', offset=336, start_offset=336, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), - Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=338, start_offset=338, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=342, start_offset=342, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=346, start_offset=346, starts_line=True, line_number=22, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='CHECK_EXC_MATCH', opcode=7, arg=None, argval=None, argrepr='', offset=356, start_offset=356, starts_line=False, line_number=22, label=None, positions=None, cache_info=None), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=14, argval=390, argrepr='to L13', offset=358, start_offset=358, starts_line=False, line_number=22, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=362, start_offset=362, starts_line=False, line_number=22, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=364, start_offset=364, starts_line=True, line_number=23, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=83, arg=9, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=374, start_offset=374, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=376, start_offset=376, starts_line=False, line_number=23, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=384, start_offset=384, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=386, start_offset=386, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), - Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=52, argval=286, argrepr='to L11', offset=388, start_offset=388, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), - Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=390, start_offset=390, starts_line=True, line_number=22, label=13, positions=None, cache_info=None), - Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=392, start_offset=392, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=394, start_offset=394, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=396, start_offset=396, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=398, start_offset=398, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=400, start_offset=400, starts_line=True, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=410, start_offset=410, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=412, start_offset=412, starts_line=False, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=420, start_offset=420, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), - Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=422, start_offset=422, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), - Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=424, start_offset=424, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=426, start_offset=426, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), - Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=428, start_offset=428, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), -] - -# One last piece of inspect fodder to check the default line number handling -def simple(): pass -expected_opinfo_simple = [ - Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=simple.__code__.co_firstlineno, label=None, positions=None), - Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=2, start_offset=2, starts_line=False, line_number=simple.__code__.co_firstlineno, label=None), -] - - -class InstructionTestCase(BytecodeTestCase): - - def assertInstructionsEqual(self, instrs_1, instrs_2, /): - instrs_1 = [instr_1._replace(positions=None, cache_info=None) for instr_1 in instrs_1] - instrs_2 = [instr_2._replace(positions=None, cache_info=None) for instr_2 in instrs_2] - self.assertEqual(instrs_1, instrs_2) - -class InstructionTests(InstructionTestCase): - - def __init__(self, *args): - super().__init__(*args) - self.maxDiff = None - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_instruction_str(self): - # smoke test for __str__ - instrs = dis.get_instructions(simple) - for instr in instrs: - str(instr) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_default_first_line(self): - actual = dis.get_instructions(simple) - self.assertInstructionsEqual(list(actual), expected_opinfo_simple) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_first_line_set_to_None(self): - actual = dis.get_instructions(simple, first_line=None) - self.assertInstructionsEqual(list(actual), expected_opinfo_simple) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_outer(self): - actual = dis.get_instructions(outer, first_line=expected_outer_line) - self.assertInstructionsEqual(list(actual), expected_opinfo_outer) - - @unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'expected_f_line' is not defined - def test_nested(self): - with captured_stdout(): - f = outer() - actual = dis.get_instructions(f, first_line=expected_f_line) - self.assertInstructionsEqual(list(actual), expected_opinfo_f) - - @unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'expected_inner_line' is not defined - def test_doubly_nested(self): - with captured_stdout(): - inner = outer()() - actual = dis.get_instructions(inner, first_line=expected_inner_line) - self.assertInstructionsEqual(list(actual), expected_opinfo_inner) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_jumpy(self): - actual = dis.get_instructions(jumpy, first_line=expected_jumpy_line) - self.assertInstructionsEqual(list(actual), expected_opinfo_jumpy) - - @requires_debug_ranges() - def test_co_positions(self): - code = compile('f(\n x, y, z\n)', '', 'exec') - positions = [ - instr.positions - for instr in dis.get_instructions(code) - ] - expected = [ - (0, 1, 0, 0), - (1, 1, 0, 1), - (1, 1, 0, 1), - (2, 2, 2, 3), - (2, 2, 5, 6), - (2, 2, 8, 9), - (1, 3, 0, 1), - (1, 3, 0, 1), - (1, 3, 0, 1) - ] - self.assertEqual(positions, expected) - - named_positions = [ - (pos.lineno, pos.end_lineno, pos.col_offset, pos.end_col_offset) - for pos in positions - ] - self.assertEqual(named_positions, expected) - - @requires_debug_ranges() - def test_co_positions_missing_info(self): - code = compile('x, y, z', '', 'exec') - code_without_location_table = code.replace(co_linetable=b'') - actual = dis.get_instructions(code_without_location_table) - for instruction in actual: - with self.subTest(instruction=instruction): - positions = instruction.positions - self.assertEqual(len(positions), 4) - if instruction.opname == "RESUME": - continue - self.assertIsNone(positions.lineno) - self.assertIsNone(positions.end_lineno) - self.assertIsNone(positions.col_offset) - self.assertIsNone(positions.end_col_offset) - - @requires_debug_ranges() - def test_co_positions_with_lots_of_caches(self): - def roots(a, b, c): - d = b**2 - 4 * a * c - yield (-b - cmath.sqrt(d)) / (2 * a) - if d: - yield (-b + cmath.sqrt(d)) / (2 * a) - code = roots.__code__ - ops = code.co_code[::2] - cache_opcode = opcode.opmap["CACHE"] - caches = sum(op == cache_opcode for op in ops) - non_caches = len(ops) - caches - # Make sure we have "lots of caches". If not, roots should be changed: - assert 1 / 3 <= caches / non_caches, "this test needs more caches!" - for show_caches in (False, True): - for adaptive in (False, True): - with self.subTest(f"{adaptive=}, {show_caches=}"): - co_positions = [ - positions - for op, positions in zip(ops, code.co_positions(), strict=True) - if show_caches or op != cache_opcode - ] - dis_positions = [ - None if instruction.positions is None else ( - instruction.positions.lineno, - instruction.positions.end_lineno, - instruction.positions.col_offset, - instruction.positions.end_col_offset, - ) - for instruction in _unroll_caches_as_Instructions(dis.get_instructions( - code, adaptive=adaptive, show_caches=show_caches - ), show_caches=show_caches) - ] - self.assertEqual(co_positions, dis_positions) - - def test_oparg_alias(self): - instruction = Instruction(opname="NOP", opcode=dis.opmap["NOP"], arg=None, argval=None, - argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, - positions=None) - self.assertEqual(instruction.arg, instruction.oparg) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_show_caches_with_label(self): - def f(x, y, z): - if x: - res = y - else: - res = z - return res - - output = io.StringIO() - dis.dis(f.__code__, file=output, show_caches=True) - self.assertIn("L1:", output.getvalue()) - - def test_baseopname_and_baseopcode(self): - # Standard instructions - for name, code in dis.opmap.items(): - instruction = Instruction(opname=name, opcode=code, arg=None, argval=None, argrepr='', offset=0, - start_offset=0, starts_line=True, line_number=1, label=None, positions=None) - baseopname = instruction.baseopname - baseopcode = instruction.baseopcode - self.assertIsNotNone(baseopname) - self.assertIsNotNone(baseopcode) - self.assertEqual(name, baseopname) - self.assertEqual(code, baseopcode) - - # Specialized instructions - for name in opcode._specialized_opmap: - instruction = Instruction(opname=name, opcode=dis._all_opmap[name], arg=None, argval=None, argrepr='', - offset=0, start_offset=0, starts_line=True, line_number=1, label=None, positions=None) - baseopname = instruction.baseopname - baseopcode = instruction.baseopcode - self.assertIn(name, opcode._specializations[baseopname]) - self.assertEqual(opcode.opmap[baseopname], baseopcode) - - def test_jump_target(self): - # Non-jump instructions should return None - instruction = Instruction(opname="NOP", opcode=dis.opmap["NOP"], arg=None, argval=None, - argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, - positions=None) - self.assertIsNone(instruction.jump_target) - - delta = 100 - instruction = Instruction(opname="JUMP_FORWARD", opcode=dis.opmap["JUMP_FORWARD"], arg=delta, argval=delta, - argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, - positions=None) - self.assertEqual(10 + 2 + 100*2, instruction.jump_target) - - # Test negative deltas - instruction = Instruction(opname="JUMP_BACKWARD", opcode=dis.opmap["JUMP_BACKWARD"], arg=delta, argval=delta, - argrepr='', offset=200, start_offset=200, starts_line=True, line_number=1, label=None, - positions=None) - self.assertEqual(200 + 2 - 100*2 + 2*1, instruction.jump_target) - - # Make sure cache entries are handled - instruction = Instruction(opname="SEND", opcode=dis.opmap["SEND"], arg=delta, argval=delta, - argrepr='', offset=10, start_offset=10, starts_line=True, line_number=1, label=None, - positions=None) - self.assertEqual(10 + 2 + 1*2 + 100*2, instruction.jump_target) - - def test_argval_argrepr(self): - def f(opcode, oparg, offset, *init_args): - arg_resolver = dis.ArgResolver(*init_args) - return arg_resolver.get_argval_argrepr(opcode, oparg, offset) - - offset = 42 - co_consts = (0, 1, 2, 3) - names = {1: 'a', 2: 'b'} - varname_from_oparg = lambda i : names[i] - labels_map = {24: 1} - args = (offset, co_consts, names, varname_from_oparg, labels_map) - self.assertEqual(f(opcode.opmap["POP_TOP"], None, *args), (None, '')) - self.assertEqual(f(opcode.opmap["LOAD_CONST"], 1, *args), (1, '1')) - self.assertEqual(f(opcode.opmap["LOAD_GLOBAL"], 2, *args), ('a', 'a')) - self.assertEqual(f(opcode.opmap["JUMP_BACKWARD"], 11, *args), (24, 'to L1')) - self.assertEqual(f(opcode.opmap["COMPARE_OP"], 3, *args), ('<', '<')) - self.assertEqual(f(opcode.opmap["SET_FUNCTION_ATTRIBUTE"], 2, *args), (2, 'kwdefaults')) - self.assertEqual(f(opcode.opmap["BINARY_OP"], 3, *args), (3, '<<')) - self.assertEqual(f(opcode.opmap["CALL_INTRINSIC_1"], 2, *args), (2, 'INTRINSIC_IMPORT_STAR')) - - def test_custom_arg_resolver(self): - class MyArgResolver(dis.ArgResolver): - def offset_from_jump_arg(self, op, arg, offset): - return arg + 1 - - def get_label_for_offset(self, offset): - return 2 * offset - - def f(opcode, oparg, offset, *init_args): - arg_resolver = MyArgResolver(*init_args) - return arg_resolver.get_argval_argrepr(opcode, oparg, offset) - offset = 42 - self.assertEqual(f(opcode.opmap["JUMP_BACKWARD"], 1, offset), (2, 'to L4')) - self.assertEqual(f(opcode.opmap["SETUP_FINALLY"], 2, offset), (3, 'to L6')) - - - def get_instructions(self, code): - return dis._get_instructions_bytes(code) - - def test_start_offset(self): - # When no extended args are present, - # start_offset should be equal to offset - - instructions = list(dis.Bytecode(_f)) - for instruction in instructions: - self.assertEqual(instruction.offset, instruction.start_offset) - - def last_item(iterable): - return functools.reduce(lambda a, b : b, iterable) - - code = bytes([ - opcode.opmap["LOAD_FAST"], 0x00, - opcode.opmap["EXTENDED_ARG"], 0x01, - opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, - ]) - labels_map = dis._make_labels_map(code) - jump = last_item(self.get_instructions(code)) - self.assertEqual(4, jump.offset) - self.assertEqual(2, jump.start_offset) - - code = bytes([ - opcode.opmap["LOAD_FAST"], 0x00, - opcode.opmap["EXTENDED_ARG"], 0x01, - opcode.opmap["EXTENDED_ARG"], 0x01, - opcode.opmap["EXTENDED_ARG"], 0x01, - opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, - opcode.opmap["CACHE"], 0x00, - ]) - jump = last_item(self.get_instructions(code)) - self.assertEqual(8, jump.offset) - self.assertEqual(2, jump.start_offset) - - code = bytes([ - opcode.opmap["LOAD_FAST"], 0x00, - opcode.opmap["EXTENDED_ARG"], 0x01, - opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, - opcode.opmap["CACHE"], 0x00, - opcode.opmap["EXTENDED_ARG"], 0x01, - opcode.opmap["EXTENDED_ARG"], 0x01, - opcode.opmap["EXTENDED_ARG"], 0x01, - opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, - opcode.opmap["CACHE"], 0x00, - ]) - instructions = list(self.get_instructions(code)) - # 1st jump - self.assertEqual(4, instructions[2].offset) - self.assertEqual(2, instructions[2].start_offset) - # 2nd jump - self.assertEqual(14, instructions[6].offset) - self.assertEqual(8, instructions[6].start_offset) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_cache_offset_and_end_offset(self): - code = bytes([ - opcode.opmap["LOAD_GLOBAL"], 0x01, - opcode.opmap["CACHE"], 0x00, - opcode.opmap["CACHE"], 0x00, - opcode.opmap["CACHE"], 0x00, - opcode.opmap["CACHE"], 0x00, - opcode.opmap["LOAD_FAST"], 0x00, - opcode.opmap["CALL"], 0x01, - opcode.opmap["CACHE"], 0x00, - opcode.opmap["CACHE"], 0x00, - opcode.opmap["CACHE"], 0x00 - ]) - instructions = list(self.get_instructions(code)) - self.assertEqual(2, instructions[0].cache_offset) - self.assertEqual(10, instructions[0].end_offset) - self.assertEqual(12, instructions[1].cache_offset) - self.assertEqual(12, instructions[1].end_offset) - self.assertEqual(14, instructions[2].cache_offset) - self.assertEqual(20, instructions[2].end_offset) - - # end_offset of the previous instruction should be equal to the - # start_offset of the following instruction - instructions = list(dis.Bytecode(self.test_cache_offset_and_end_offset)) - for prev, curr in zip(instructions, instructions[1:]): - self.assertEqual(prev.end_offset, curr.start_offset) - - -# get_instructions has its own tests above, so can rely on it to validate -# the object oriented API -class BytecodeTests(InstructionTestCase, DisTestBase): - - def test_instantiation(self): - # Test with function, method, code string and code object - for obj in [_f, _C(1).__init__, "a=1", _f.__code__]: - with self.subTest(obj=obj): - b = dis.Bytecode(obj) - self.assertIsInstance(b.codeobj, types.CodeType) - - self.assertRaises(TypeError, dis.Bytecode, object()) - - def test_iteration(self): - for obj in [_f, _C(1).__init__, "a=1", _f.__code__]: - with self.subTest(obj=obj): - via_object = list(dis.Bytecode(obj)) - via_generator = list(dis.get_instructions(obj)) - self.assertInstructionsEqual(via_object, via_generator) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_explicit_first_line(self): - actual = dis.Bytecode(outer, first_line=expected_outer_line) - self.assertInstructionsEqual(list(actual), expected_opinfo_outer) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_source_line_in_disassembly(self): - # Use the line in the source code - actual = dis.Bytecode(simple).dis() - actual = actual.strip().partition(" ")[0] # extract the line no - expected = str(simple.__code__.co_firstlineno) - self.assertEqual(actual, expected) - # Use an explicit first line number - actual = dis.Bytecode(simple, first_line=350).dis() - actual = actual.strip().partition(" ")[0] # extract the line no - self.assertEqual(actual, "350") - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_info(self): - self.maxDiff = 1000 - for x, expected in CodeInfoTests.test_pairs: - b = dis.Bytecode(x) - self.assertRegex(b.info(), expected) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_disassembled(self): - actual = dis.Bytecode(_f).dis() - self.do_disassembly_compare(actual, dis_f) - - def test_from_traceback(self): - tb = get_tb() - b = dis.Bytecode.from_traceback(tb) - while tb.tb_next: tb = tb.tb_next - - self.assertEqual(b.current_offset, tb.tb_lasti) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_from_traceback_dis(self): - self.maxDiff = None - tb = get_tb() - b = dis.Bytecode.from_traceback(tb) - self.assertEqual(b.dis(), dis_traceback) - - @requires_debug_ranges() - def test_bytecode_co_positions(self): - bytecode = dis.Bytecode("a=1") - for instr, positions in zip(bytecode, bytecode.codeobj.co_positions()): - assert instr.positions == positions - -class TestBytecodeTestCase(BytecodeTestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_assert_not_in_with_op_not_in_bytecode(self): - code = compile("a = 1", "", "exec") - self.assertInBytecode(code, "LOAD_CONST", 1) - self.assertNotInBytecode(code, "LOAD_NAME") - self.assertNotInBytecode(code, "LOAD_NAME", "a") - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_assert_not_in_with_arg_not_in_bytecode(self): - code = compile("a = 1", "", "exec") - self.assertInBytecode(code, "LOAD_CONST") - self.assertInBytecode(code, "LOAD_CONST", 1) - self.assertNotInBytecode(code, "LOAD_CONST", 2) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_assert_not_in_with_arg_in_bytecode(self): - code = compile("a = 1", "", "exec") - with self.assertRaises(AssertionError): - self.assertNotInBytecode(code, "LOAD_CONST", 1) - -class TestFinderMethods(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON - def test__find_imports(self): - cases = [ - ("import a.b.c", ('a.b.c', 0, None)), - ("from a.b import c", ('a.b', 0, ('c',))), - ("from a.b import c as d", ('a.b', 0, ('c',))), - ("from a.b import *", ('a.b', 0, ('*',))), - ("from ...a.b import c as d", ('a.b', 3, ('c',))), - ("from ..a.b import c as d, e as f", ('a.b', 2, ('c', 'e'))), - ("from ..a.b import *", ('a.b', 2, ('*',))), - ] - for src, expected in cases: - with self.subTest(src=src): - code = compile(src, "", "exec") - res = tuple(dis._find_imports(code)) - self.assertEqual(len(res), 1) - self.assertEqual(res[0], expected) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test__find_store_names(self): - cases = [ - ("x+y", ()), - ("x=y=1", ('x', 'y')), - ("x+=y", ('x',)), - ("global x\nx=y=1", ('x', 'y')), - ("global x\nz=x", ('z',)), - ] - for src, expected in cases: - with self.subTest(src=src): - code = compile(src, "", "exec") - res = tuple(dis._find_store_names(code)) - self.assertEqual(res, expected) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_findlabels(self): - labels = dis.findlabels(jumpy.__code__.co_code) - jumps = [ - instr.offset - for instr in expected_opinfo_jumpy - if instr.is_jump_target - ] - - self.assertEqual(sorted(labels), sorted(jumps)) - - def test_findlinestarts(self): - def func(): - pass - - code = func.__code__ - offsets = [linestart[0] for linestart in dis.findlinestarts(code)] - self.assertEqual(offsets, [0, 2]) - - -class TestDisTraceback(DisTestBase): - def setUp(self) -> None: - try: # We need to clean up existing tracebacks - del sys.last_exc - except AttributeError: - pass - try: # We need to clean up existing tracebacks - del sys.last_traceback - except AttributeError: - pass - return super().setUp() - - def get_disassembly(self, tb): - output = io.StringIO() - with contextlib.redirect_stdout(output): - dis.distb(tb) - return output.getvalue() - - def test_distb_empty(self): - with self.assertRaises(RuntimeError): - dis.distb() - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_distb_last_traceback(self): - self.maxDiff = None - # We need to have an existing last traceback in `sys`: - tb = get_tb() - sys.last_traceback = tb - - self.do_disassembly_compare(self.get_disassembly(None), dis_traceback) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_distb_explicit_arg(self): - self.maxDiff = None - tb = get_tb() - - self.do_disassembly_compare(self.get_disassembly(tb), dis_traceback) - - -class TestDisTracebackWithFile(TestDisTraceback): - # Run the `distb` tests again, using the file arg instead of print - def get_disassembly(self, tb): - output = io.StringIO() - with contextlib.redirect_stdout(output): - dis.distb(tb, file=output) - return output.getvalue() - -def _unroll_caches_as_Instructions(instrs, show_caches=False): - # Cache entries are no longer reported by dis as fake instructions, - # but some tests assume that do. We should rewrite the tests to assume - # the new API, but it will be clearer to keep the tests working as - # before and do that in a separate PR. - - for instr in instrs: - yield instr - if not show_caches: - continue - - offset = instr.offset - for name, size, data in (instr.cache_info or ()): - for i in range(size): - offset += 2 - # Only show the fancy argrepr for a CACHE instruction when it's - # the first entry for a particular cache value: - if i == 0: - argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" - else: - argrepr = "" - - yield Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, - False, None, None, instr.positions) - - -class TestDisCLI(unittest.TestCase): - - def setUp(self): - self.filename = tempfile.mktemp() - self.addCleanup(os_helper.unlink, self.filename) - - @staticmethod - def text_normalize(string): - """Dedent *string* and strip it from its surrounding whitespaces. - - This method is used by the other utility functions so that any - string to write or to match against can be freely indented. - """ - return textwrap.dedent(string).strip() - - def set_source(self, content): - with open(self.filename, 'w') as fp: - fp.write(self.text_normalize(content)) - - def invoke_dis(self, *flags): - output = io.StringIO() - with contextlib.redirect_stdout(output): - dis.main(args=[*flags, self.filename]) - return self.text_normalize(output.getvalue()) - - def check_output(self, source, expect, *flags): - with self.subTest(source=source, flags=flags): - self.set_source(source) - res = self.invoke_dis(*flags) - expect = self.text_normalize(expect) - self.assertListEqual(res.splitlines(), expect.splitlines()) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_invocation(self): - # test various combinations of parameters - base_flags = [ - ('-C', '--show-caches'), - ('-O', '--show-offsets'), - ] - - self.set_source(''' - def f(): - print(x) - return None - ''') - - for r in range(1, len(base_flags) + 1): - for choices in itertools.combinations(base_flags, r=r): - for args in itertools.product(*choices): - with self.subTest(args=args[1:]): - _ = self.invoke_dis(*args) - - with self.assertRaises(SystemExit): - # suppress argparse error message - with contextlib.redirect_stderr(io.StringIO()): - _ = self.invoke_dis('--unknown') - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_show_cache(self): - # test 'python -m dis -C/--show-caches' - source = 'print()' - expect = ''' - 0 RESUME 0 - - 1 LOAD_NAME 0 (print) - PUSH_NULL - CALL 0 - CACHE 0 (counter: 0) - CACHE 0 (func_version: 0) - CACHE 0 - POP_TOP - RETURN_CONST 0 (None) - ''' - for flag in ['-C', '--show-caches']: - self.check_output(source, expect, flag) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'code' object has no attribute '_varname_from_oparg' - def test_show_offsets(self): - # test 'python -m dis -O/--show-offsets' - source = 'pass' - expect = ''' - 0 0 RESUME 0 - - 1 2 RETURN_CONST 0 (None) - ''' - for flag in ['-O', '--show-offsets']: - self.check_output(source, expect, flag) + test_code = f""" +{self.setup} +dis.dis(tested_func) +dis.dis("x = 2; print(x)") +""" + + result = subprocess.run( + self.command + (test_code,), capture_output=True + ) + self.assertNotEqual("", result.stdout.decode()) + self.assertEqual("", result.stderr.decode()) + + def test_disassemble(self): + test_code = f""" +{self.setup} +dis.disassemble(tested_func) +""" + result = subprocess.run( + self.command + (test_code,), capture_output=True + ) + # In CPython this would raise an AttributeError, not a + # TypeError because dis is implemented in python in CPython and + # as such the type mismatch wouldn't be caught immeadiately + self.assertIn("TypeError", result.stderr.decode()) + + test_code = f""" +{self.setup} +dis.disassemble(tested_func.__code__) +""" + result = subprocess.run( + self.command + (test_code,), capture_output=True + ) + self.assertNotEqual("", result.stdout.decode()) + self.assertEqual("", result.stderr.decode()) if __name__ == "__main__": From a7b9356da2a85cbc02df1f0fac9eda6a26869b6d Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:19:32 +0200 Subject: [PATCH 16/16] Mark failing tests --- Lib/test/test__opcode.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index dd4f30ab17..b1e38b43dc 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -27,6 +27,7 @@ def test_invalid_opcodes(self): self.check_bool_function_result(_opcode.has_local, invalid, False) self.check_bool_function_result(_opcode.has_exc, invalid, False) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'opmap' def test_is_valid(self): names = [ 'CACHE', @@ -38,6 +39,7 @@ def test_is_valid(self): opcodes = [dis.opmap[opname] for opname in names] self.check_bool_function_result(_opcode.is_valid, opcodes, True) + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'dis' has no attribute 'hasarg' def test_oplists(self): def check_function(self, func, expected): for op in [-10, 520]: