mirror of
https://github.com/python/cpython.git
synced 2025-07-22 18:55:22 +00:00

Inlining of code that corresponds to source code lines, can make it hard to distinguish later between code which is only reachable from except handlers, and that which is reachable in normal control flow. This caused problems with the debugger's jump feature.
This PR turns off the inlining optimisation for code which has line numbers. We still inline things like the implicit "return None"..
(cherry picked from commit bde06e1b83
)
Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
1830 lines
77 KiB
Python
1830 lines
77 KiB
Python
# Minimal tests for dis module
|
|
|
|
import contextlib
|
|
import dis
|
|
import io
|
|
import re
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from test.support import captured_stdout, requires_debug_ranges, cpython_only
|
|
from test.support.bytecode_helper import BytecodeTestCase
|
|
|
|
import opcode
|
|
|
|
|
|
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
|
|
|
|
class _C:
|
|
def __init__(self, x):
|
|
self.x = x == 1
|
|
|
|
@staticmethod
|
|
def sm(x):
|
|
x = x == 1
|
|
|
|
@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 2 (==)
|
|
LOAD_FAST 0 (self)
|
|
STORE_ATTR 0 (x)
|
|
LOAD_CONST 0 (None)
|
|
RETURN_VALUE
|
|
""" % (_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 2 (==)
|
|
LOAD_FAST 0
|
|
STORE_ATTR 0
|
|
LOAD_CONST 0
|
|
RETURN_VALUE
|
|
"""
|
|
|
|
dis_c_class_method = """\
|
|
%3d RESUME 0
|
|
|
|
%3d LOAD_FAST 1 (x)
|
|
LOAD_CONST 1 (1)
|
|
COMPARE_OP 2 (==)
|
|
LOAD_FAST 0 (cls)
|
|
STORE_ATTR 0 (x)
|
|
LOAD_CONST 0 (None)
|
|
RETURN_VALUE
|
|
""" % (_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 2 (==)
|
|
STORE_FAST 0 (x)
|
|
LOAD_CONST 0 (None)
|
|
RETURN_VALUE
|
|
""" % (_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 (NULL + print)
|
|
LOAD_FAST 0 (a)
|
|
PRECALL 1
|
|
CALL 1
|
|
POP_TOP
|
|
|
|
%3d LOAD_CONST 1 (1)
|
|
RETURN_VALUE
|
|
""" % (_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
|
|
PRECALL 1
|
|
CALL 1
|
|
POP_TOP
|
|
LOAD_CONST 1
|
|
RETURN_VALUE
|
|
"""
|
|
|
|
|
|
def bug708901():
|
|
for res in range(1,
|
|
10):
|
|
pass
|
|
|
|
dis_bug708901 = """\
|
|
%3d RESUME 0
|
|
|
|
%3d LOAD_GLOBAL 1 (NULL + range)
|
|
LOAD_CONST 1 (1)
|
|
|
|
%3d LOAD_CONST 2 (10)
|
|
|
|
%3d PRECALL 2
|
|
CALL 2
|
|
GET_ITER
|
|
>> FOR_ITER 2 (to 40)
|
|
STORE_FAST 0 (res)
|
|
|
|
%3d JUMP_BACKWARD 3 (to 34)
|
|
|
|
%3d >> LOAD_CONST 0 (None)
|
|
RETURN_VALUE
|
|
""" % (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 2 (<code object <listcomp> at 0x..., file "%s", line %d>)
|
|
MAKE_FUNCTION 0
|
|
LOAD_FAST 0 (x)
|
|
GET_ITER
|
|
PRECALL 0
|
|
CALL 0
|
|
|
|
%3d LOAD_CONST 3 (1)
|
|
|
|
%3d BINARY_OP 0 (+)
|
|
PRECALL 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
|
|
LOAD_CONST 0 (None)
|
|
RETURN_VALUE
|
|
"""
|
|
|
|
# Extended arg followed by NOP
|
|
code_bug_45757 = bytes([
|
|
0x90, 0x01, # EXTENDED_ARG 0x01
|
|
0x09, 0xFF, # NOP 0xFF
|
|
0x90, 0x01, # EXTENDED_ARG 0x01
|
|
0x64, 0x29, # LOAD_CONST 0x29
|
|
0x53, 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 = """\
|
|
>> EXTENDED_ARG 255
|
|
EXTENDED_ARG 65535
|
|
EXTENDED_ARG 16777215
|
|
JUMP_FORWARD -4 (to 0)
|
|
"""
|
|
|
|
_BIG_LINENO_FORMAT = """\
|
|
1 RESUME 0
|
|
|
|
%3d LOAD_GLOBAL 0 (spam)
|
|
POP_TOP
|
|
LOAD_CONST 0 (None)
|
|
RETURN_VALUE
|
|
"""
|
|
|
|
_BIG_LINENO_FORMAT2 = """\
|
|
1 RESUME 0
|
|
|
|
%4d LOAD_GLOBAL 0 (spam)
|
|
POP_TOP
|
|
LOAD_CONST 0 (None)
|
|
RETURN_VALUE
|
|
"""
|
|
|
|
dis_module_expected_results = """\
|
|
Disassembly of f:
|
|
4 RESUME 0
|
|
LOAD_CONST 0 (None)
|
|
RETURN_VALUE
|
|
|
|
Disassembly of g:
|
|
5 RESUME 0
|
|
LOAD_CONST 0 (None)
|
|
RETURN_VALUE
|
|
|
|
"""
|
|
|
|
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)
|
|
LOAD_CONST 1 (None)
|
|
RETURN_VALUE
|
|
"""
|
|
|
|
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 PUSH_NULL
|
|
LOAD_NAME 3 (fun)
|
|
LOAD_CONST 0 (1)
|
|
PRECALL 1
|
|
CALL 1
|
|
LOAD_NAME 2 (__annotations__)
|
|
LOAD_CONST 2 ('y')
|
|
STORE_SUBSCR
|
|
|
|
4 LOAD_CONST 0 (1)
|
|
LOAD_NAME 4 (lst)
|
|
PUSH_NULL
|
|
LOAD_NAME 3 (fun)
|
|
LOAD_CONST 3 (0)
|
|
PRECALL 1
|
|
CALL 1
|
|
STORE_SUBSCR
|
|
LOAD_NAME 1 (int)
|
|
POP_TOP
|
|
LOAD_CONST 4 (None)
|
|
RETURN_VALUE
|
|
"""
|
|
|
|
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 >> LOAD_NAME 0 (x)
|
|
LOAD_CONST 1 (1)
|
|
BINARY_OP 13 (+=)
|
|
STORE_NAME 0 (x)
|
|
|
|
2 JUMP_BACKWARD 6 (to 8)
|
|
"""
|
|
|
|
dis_traceback = """\
|
|
%3d RESUME 0
|
|
|
|
%3d NOP
|
|
|
|
%3d LOAD_CONST 1 (1)
|
|
LOAD_CONST 2 (0)
|
|
--> BINARY_OP 11 (/)
|
|
POP_TOP
|
|
JUMP_FORWARD 30 (to 76)
|
|
>> PUSH_EXC_INFO
|
|
|
|
%3d LOAD_GLOBAL 0 (Exception)
|
|
CHECK_EXC_MATCH
|
|
POP_JUMP_FORWARD_IF_FALSE 17 (to 68)
|
|
STORE_FAST 0 (e)
|
|
|
|
%3d LOAD_FAST 0 (e)
|
|
LOAD_ATTR 1 (__traceback__)
|
|
STORE_FAST 1 (tb)
|
|
POP_EXCEPT
|
|
LOAD_CONST 0 (None)
|
|
STORE_FAST 0 (e)
|
|
DELETE_FAST 0 (e)
|
|
JUMP_FORWARD 8 (to 76)
|
|
>> LOAD_CONST 0 (None)
|
|
STORE_FAST 0 (e)
|
|
DELETE_FAST 0 (e)
|
|
RERAISE 1
|
|
|
|
%3d >> RERAISE 0
|
|
>> COPY 3
|
|
POP_EXCEPT
|
|
RERAISE 1
|
|
|
|
%3d >> LOAD_FAST 1 (tb)
|
|
RETURN_VALUE
|
|
ExceptionTable:
|
|
""" % (TRACEBACK_CODE.co_firstlineno,
|
|
TRACEBACK_CODE.co_firstlineno + 1,
|
|
TRACEBACK_CODE.co_firstlineno + 2,
|
|
TRACEBACK_CODE.co_firstlineno + 3,
|
|
TRACEBACK_CODE.co_firstlineno + 4,
|
|
TRACEBACK_CODE.co_firstlineno + 3,
|
|
TRACEBACK_CODE.co_firstlineno + 5)
|
|
|
|
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_VALUE 0
|
|
LOAD_CONST 1 (' ')
|
|
LOAD_FAST 1 (b)
|
|
LOAD_CONST 2 ('4')
|
|
FORMAT_VALUE 4 (with format)
|
|
LOAD_CONST 1 (' ')
|
|
LOAD_FAST 2 (c)
|
|
FORMAT_VALUE 2 (repr)
|
|
LOAD_CONST 1 (' ')
|
|
LOAD_FAST 3 (d)
|
|
LOAD_CONST 2 ('4')
|
|
FORMAT_VALUE 6 (repr, with format)
|
|
BUILD_STRING 7
|
|
RETURN_VALUE
|
|
""" % (_fstring.__code__.co_firstlineno, _fstring.__code__.co_firstlineno + 1)
|
|
|
|
def _tryfinally(a, b):
|
|
try:
|
|
return a
|
|
finally:
|
|
b()
|
|
|
|
def _tryfinallyconst(b):
|
|
try:
|
|
return 1
|
|
finally:
|
|
b()
|
|
|
|
dis_tryfinally = """\
|
|
%3d RESUME 0
|
|
|
|
%3d NOP
|
|
|
|
%3d LOAD_FAST 0 (a)
|
|
|
|
%3d PUSH_NULL
|
|
LOAD_FAST 1 (b)
|
|
PRECALL 0
|
|
CALL 0
|
|
POP_TOP
|
|
RETURN_VALUE
|
|
>> PUSH_EXC_INFO
|
|
PUSH_NULL
|
|
LOAD_FAST 1 (b)
|
|
PRECALL 0
|
|
CALL 0
|
|
POP_TOP
|
|
RERAISE 0
|
|
>> COPY 3
|
|
POP_EXCEPT
|
|
RERAISE 1
|
|
ExceptionTable:
|
|
""" % (_tryfinally.__code__.co_firstlineno,
|
|
_tryfinally.__code__.co_firstlineno + 1,
|
|
_tryfinally.__code__.co_firstlineno + 2,
|
|
_tryfinally.__code__.co_firstlineno + 4,
|
|
)
|
|
|
|
dis_tryfinallyconst = """\
|
|
%3d RESUME 0
|
|
|
|
%3d NOP
|
|
|
|
%3d NOP
|
|
|
|
%3d PUSH_NULL
|
|
LOAD_FAST 0 (b)
|
|
PRECALL 0
|
|
CALL 0
|
|
POP_TOP
|
|
LOAD_CONST 1 (1)
|
|
RETURN_VALUE
|
|
PUSH_EXC_INFO
|
|
PUSH_NULL
|
|
LOAD_FAST 0 (b)
|
|
PRECALL 0
|
|
CALL 0
|
|
POP_TOP
|
|
RERAISE 0
|
|
>> COPY 3
|
|
POP_EXCEPT
|
|
RERAISE 1
|
|
ExceptionTable:
|
|
""" % (_tryfinallyconst.__code__.co_firstlineno,
|
|
_tryfinallyconst.__code__.co_firstlineno + 1,
|
|
_tryfinallyconst.__code__.co_firstlineno + 2,
|
|
_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 [x + z for z in y]
|
|
return foo
|
|
|
|
dis_nested_0 = """\
|
|
MAKE_CELL 0 (y)
|
|
|
|
%3d RESUME 0
|
|
|
|
%3d LOAD_CLOSURE 0 (y)
|
|
BUILD_TUPLE 1
|
|
LOAD_CONST 1 (<code object foo at 0x..., file "%s", line %d>)
|
|
MAKE_FUNCTION 8 (closure)
|
|
STORE_FAST 1 (foo)
|
|
|
|
%3d 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 <code object foo at 0x..., file "%s", line %d>:
|
|
COPY_FREE_VARS 1
|
|
MAKE_CELL 0 (x)
|
|
|
|
%3d RESUME 0
|
|
|
|
%3d LOAD_CLOSURE 0 (x)
|
|
BUILD_TUPLE 1
|
|
LOAD_CONST 1 (<code object <listcomp> at 0x..., file "%s", line %d>)
|
|
MAKE_FUNCTION 8 (closure)
|
|
LOAD_DEREF 1 (y)
|
|
GET_ITER
|
|
PRECALL 0
|
|
CALL 0
|
|
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 <code object <listcomp> at 0x..., file "%s", line %d>:
|
|
COPY_FREE_VARS 1
|
|
|
|
%3d RESUME 0
|
|
BUILD_LIST 0
|
|
LOAD_FAST 0 (.0)
|
|
>> FOR_ITER 7 (to 24)
|
|
STORE_FAST 1 (z)
|
|
LOAD_DEREF 2 (x)
|
|
LOAD_FAST 1 (z)
|
|
BINARY_OP 0 (+)
|
|
LIST_APPEND 2
|
|
JUMP_BACKWARD 8 (to 8)
|
|
>> RETURN_VALUE
|
|
""" % (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 0 RESUME_QUICK 0
|
|
|
|
%3d 2 LOAD_FAST__LOAD_FAST 0 (x)
|
|
4 LOAD_FAST 1 (y)
|
|
6 STORE_FAST__STORE_FAST 3 (b)
|
|
8 STORE_FAST__LOAD_FAST 2 (a)
|
|
|
|
%3d 10 LOAD_FAST__LOAD_FAST 2 (a)
|
|
12 LOAD_FAST 3 (b)
|
|
14 BUILD_TUPLE 2
|
|
16 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 0 RESUME_QUICK 0
|
|
|
|
%3d 2 BUILD_LIST 0
|
|
4 LOAD_CONST 1 ((1, 2, 3))
|
|
6 LIST_EXTEND 1
|
|
8 LOAD_CONST 2 (3)
|
|
10 BINARY_OP_ADAPTIVE 5 (*)
|
|
14 GET_ITER
|
|
16 FOR_ITER 17 (to 52)
|
|
18 STORE_FAST 0 (i)
|
|
|
|
%3d 20 LOAD_GLOBAL_MODULE 1 (NULL + load_test)
|
|
32 LOAD_FAST 0 (i)
|
|
34 PRECALL_PYFUNC 1
|
|
38 CALL_PY_WITH_DEFAULTS 1
|
|
48 POP_TOP
|
|
50 JUMP_BACKWARD_QUICK 18 (to 16)
|
|
|
|
%3d >> 52 LOAD_CONST 0 (None)
|
|
54 RETURN_VALUE
|
|
""" % (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 0 RESUME 0
|
|
|
|
%3d 2 LOAD_CONST 1 (Ellipsis)
|
|
4 EXTENDED_ARG_QUICK 1
|
|
6 UNPACK_EX 256
|
|
8 STORE_FAST 0 (_)
|
|
10 STORE_FAST 0 (_)
|
|
12 LOAD_CONST 0 (None)
|
|
14 RETURN_VALUE
|
|
"""% (extended_arg_quick.__code__.co_firstlineno,
|
|
extended_arg_quick.__code__.co_firstlineno + 1,)
|
|
|
|
QUICKENING_WARMUP_DELAY = 8
|
|
|
|
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 find_offset_column(self, lines):
|
|
for line in lines:
|
|
if line and not line.startswith("Disassembly"):
|
|
break
|
|
else:
|
|
return 0, 0
|
|
offset = 5
|
|
while (line[offset] == " "):
|
|
offset += 1
|
|
if (line[offset] == ">"):
|
|
offset += 2
|
|
while (line[offset] == " "):
|
|
offset += 1
|
|
end = offset
|
|
while line[end] in "0123456789":
|
|
end += 1
|
|
return end-5, end
|
|
|
|
def assert_offsets_increasing(self, text, delta):
|
|
expected_offset = 0
|
|
lines = text.splitlines()
|
|
start, end = self.find_offset_column(lines)
|
|
for line in lines:
|
|
if not line:
|
|
continue
|
|
if line.startswith("Disassembly"):
|
|
expected_offset = 0
|
|
continue
|
|
if line.startswith("Exception"):
|
|
break
|
|
offset = int(line[start:end])
|
|
self.assertGreaterEqual(offset, expected_offset, line)
|
|
expected_offset = offset + delta
|
|
|
|
def strip_offsets(self, text):
|
|
lines = text.splitlines(True)
|
|
start, end = self.find_offset_column(lines)
|
|
res = []
|
|
lines = iter(lines)
|
|
for line in lines:
|
|
if line.startswith("Exception"):
|
|
res.append(line)
|
|
break
|
|
if not line or line.startswith("Disassembly"):
|
|
res.append(line)
|
|
else:
|
|
res.append(line[:start] + line[end:])
|
|
return "".join(res)
|
|
|
|
def do_disassembly_compare(self, got, expected, with_offsets=False):
|
|
if not with_offsets:
|
|
self.assert_offsets_increasing(got, 2)
|
|
got = self.strip_offsets(got)
|
|
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, with_offsets=False):
|
|
self.maxDiff = None
|
|
got = self.get_disassembly(func, depth=0)
|
|
self.do_disassembly_compare(got, expected, with_offsets)
|
|
|
|
def test_opmap(self):
|
|
self.assertEqual(dis.opmap["NOP"], 9)
|
|
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)
|
|
self.assertEqual(dis.opmap["STORE_NAME"], dis.HAVE_ARGUMENT)
|
|
|
|
def test_widths(self):
|
|
long_opcodes = set(['POP_JUMP_FORWARD_IF_FALSE',
|
|
'POP_JUMP_FORWARD_IF_TRUE',
|
|
'POP_JUMP_FORWARD_IF_NOT_NONE',
|
|
'POP_JUMP_FORWARD_IF_NONE',
|
|
'POP_JUMP_BACKWARD_IF_FALSE',
|
|
'POP_JUMP_BACKWARD_IF_TRUE',
|
|
'POP_JUMP_BACKWARD_IF_NOT_NONE',
|
|
'POP_JUMP_BACKWARD_IF_NONE',
|
|
'JUMP_BACKWARD_NO_INTERRUPT',
|
|
])
|
|
for opcode, opname in enumerate(dis.opname):
|
|
if opname in long_opcodes:
|
|
continue
|
|
with self.subTest(opname=opname):
|
|
width = dis._OPNAME_WIDTH
|
|
if opcode < dis.HAVE_ARGUMENT:
|
|
width += 1 + dis._OPARG_WIDTH
|
|
self.assertLessEqual(len(opname), width)
|
|
|
|
def test_dis(self):
|
|
self.do_disassembly_test(_f, dis_f)
|
|
|
|
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_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_big_offsets(self):
|
|
self.maxDiff = None
|
|
def func(count):
|
|
namespace = {}
|
|
func = "def foo(x):\n " + ";".join(["x = x + 1"] * count) + "\n return x"
|
|
exec(func, namespace)
|
|
return namespace['foo']
|
|
|
|
def expected(count, w):
|
|
s = ['''\
|
|
1 %*d RESUME 0
|
|
|
|
''' % (w, 0)]
|
|
s += ['''\
|
|
%*d LOAD_FAST 0 (x)
|
|
%*d LOAD_CONST 1 (1)
|
|
%*d BINARY_OP 0 (+)
|
|
%*d STORE_FAST 0 (x)
|
|
''' % (w, 10*i + 2, w, 10*i + 4, w, 10*i + 6, w, 10*i + 10)
|
|
for i in range(count)]
|
|
s += ['''\
|
|
|
|
3 %*d LOAD_FAST 0 (x)
|
|
%*d RETURN_VALUE
|
|
''' % (w, 10*count + 2, w, 10*count + 4)]
|
|
s[1] = ' 2' + s[1][3:]
|
|
return ''.join(s)
|
|
|
|
for i in range(1, 5):
|
|
self.do_disassembly_test(func(i), expected(i, 4), True)
|
|
self.do_disassembly_test(func(999), expected(999, 4), True)
|
|
self.do_disassembly_test(func(1000), expected(1000, 5), True)
|
|
|
|
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_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_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_traceback = tb
|
|
|
|
tb_dis = self.get_disassemble_as_string(tb.tb_frame.f_code, tb.tb_lasti)
|
|
self.do_disassembly_test(None, tb_dis, True)
|
|
|
|
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)
|
|
dis = self.strip_offsets(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)
|
|
|
|
@staticmethod
|
|
def code_quicken(f, times=QUICKENING_WARMUP_DELAY):
|
|
for _ in range(times):
|
|
f()
|
|
|
|
@cpython_only
|
|
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, True)
|
|
|
|
@cpython_only
|
|
def test_binary_specialize(self):
|
|
binary_op_quicken = """\
|
|
0 0 RESUME_QUICK 0
|
|
|
|
1 2 LOAD_NAME 0 (a)
|
|
4 LOAD_NAME 1 (b)
|
|
6 %s
|
|
10 RETURN_VALUE
|
|
"""
|
|
co_int = compile('a + b', "<int>", "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 (+)", True)
|
|
|
|
co_unicode = compile('a + b', "<unicode>", "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 (+)", True)
|
|
|
|
binary_subscr_quicken = """\
|
|
0 0 RESUME_QUICK 0
|
|
|
|
1 2 LOAD_NAME 0 (a)
|
|
4 LOAD_CONST 0 (0)
|
|
6 %s
|
|
16 RETURN_VALUE
|
|
"""
|
|
co_list = compile('a[0]', "<list>", "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", True)
|
|
|
|
co_dict = compile('a[0]', "<dict>", "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", True)
|
|
|
|
@cpython_only
|
|
def test_load_attr_specialize(self):
|
|
load_attr_quicken = """\
|
|
0 0 RESUME_QUICK 0
|
|
|
|
1 2 LOAD_CONST 0 ('a')
|
|
4 LOAD_ATTR_SLOT 0 (__class__)
|
|
14 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, True)
|
|
|
|
@cpython_only
|
|
def test_call_specialize(self):
|
|
call_quicken = """\
|
|
0 0 RESUME_QUICK 0
|
|
|
|
1 2 PUSH_NULL
|
|
4 LOAD_NAME 0 (str)
|
|
6 LOAD_CONST 0 (1)
|
|
8 PRECALL_NO_KW_STR_1 1
|
|
12 CALL_ADAPTIVE 1
|
|
22 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, True)
|
|
|
|
@cpython_only
|
|
def test_loop_quicken(self):
|
|
# Loop can trigger a quicken where the loop is located
|
|
self.code_quicken(loop_test, 1)
|
|
got = self.get_disassembly(loop_test, adaptive=True)
|
|
self.do_disassembly_compare(got, dis_loop_test_quickened_code, True)
|
|
|
|
@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, True)
|
|
|
|
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 dis.get_instructions(
|
|
f, show_caches=True, adaptive=adaptive
|
|
):
|
|
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 quickened and 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 = 25
|
|
empty_caches = 8 if adaptive and quickened else total_caches
|
|
self.assertEqual(caches.count(""), empty_caches)
|
|
self.assertEqual(len(caches), total_caches)
|
|
|
|
|
|
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 sys.flags.optimize:
|
|
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: <code object f at (.*), file "(.*)", line (.*)>
|
|
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: <module>
|
|
Filename: <disassembly>
|
|
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: <module>
|
|
Filename: <disassembly>
|
|
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: <module>
|
|
Filename: <disassembly>
|
|
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[3]
|
|
expected_f_line = code_object_f.co_firstlineno - _line_offset
|
|
code_object_inner = code_object_f.co_consts[3]
|
|
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 str(instr._replace(positions=None))
|
|
|
|
def _prepare_test_cases():
|
|
_instructions = dis.get_instructions(outer, first_line=expected_outer_line)
|
|
print('expected_opinfo_outer = [\n ',
|
|
',\n '.join(map(_stringify_instruction, _instructions)), ',\n]', sep='')
|
|
_instructions = dis.get_instructions(outer(), first_line=expected_f_line)
|
|
print('expected_opinfo_f = [\n ',
|
|
',\n '.join(map(_stringify_instruction, _instructions)), ',\n]', sep='')
|
|
_instructions = dis.get_instructions(outer()(), first_line=expected_inner_line)
|
|
print('expected_opinfo_inner = [\n ',
|
|
',\n '.join(map(_stringify_instruction, _instructions)), ',\n]', sep='')
|
|
_instructions = dis.get_instructions(jumpy, first_line=expected_jumpy_line)
|
|
print('expected_opinfo_jumpy = [\n ',
|
|
',\n '.join(map(_stringify_instruction, _instructions)), ',\n]', sep='')
|
|
dis.dis(outer)
|
|
|
|
#_prepare_test_cases()
|
|
|
|
Instruction = dis.Instruction
|
|
expected_opinfo_outer = [
|
|
Instruction(opname='MAKE_CELL', opcode=135, arg=0, argval='a', argrepr='a', offset=0, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='MAKE_CELL', opcode=135, arg=1, argval='b', argrepr='b', offset=2, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RESUME', opcode=151, arg=0, argval=0, argrepr='', offset=4, starts_line=1, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval=(3, 4), argrepr='(3, 4)', offset=6, starts_line=2, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=0, argval='a', argrepr='a', offset=8, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=1, argval='b', argrepr='b', offset=10, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='BUILD_TUPLE', opcode=102, arg=2, argval=2, argrepr='', offset=12, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_f, argrepr=repr(code_object_f), offset=14, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='MAKE_FUNCTION', opcode=132, arg=9, argval=9, argrepr='defaults, closure', offset=16, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='f', argrepr='f', offset=18, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='NULL + print', offset=20, starts_line=7, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=0, argval='a', argrepr='a', offset=32, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=1, argval='b', argrepr='b', offset=34, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='', argrepr="''", offset=36, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=38, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='BUILD_LIST', opcode=103, arg=0, argval=0, argrepr='', offset=40, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='BUILD_MAP', opcode=105, arg=0, argval=0, argrepr='', offset=42, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Hello world!', argrepr="'Hello world!'", offset=44, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=7, argval=7, argrepr='', offset=46, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=7, argval=7, argrepr='', offset=50, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=60, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='f', argrepr='f', offset=62, starts_line=8, is_jump_target=False, positions=None),
|
|
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=64, starts_line=None, is_jump_target=False, positions=None),
|
|
]
|
|
|
|
expected_opinfo_f = [
|
|
Instruction(opname='COPY_FREE_VARS', opcode=149, arg=2, argval=2, argrepr='', offset=0, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='MAKE_CELL', opcode=135, arg=0, argval='c', argrepr='c', offset=2, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='MAKE_CELL', opcode=135, arg=1, argval='d', argrepr='d', offset=4, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RESUME', opcode=151, arg=0, argval=0, argrepr='', offset=6, starts_line=2, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval=(5, 6), argrepr='(5, 6)', offset=8, starts_line=3, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=3, argval='a', argrepr='a', offset=10, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=4, argval='b', argrepr='b', offset=12, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=0, argval='c', argrepr='c', offset=14, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=1, argval='d', argrepr='d', offset=16, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='BUILD_TUPLE', opcode=102, arg=4, argval=4, argrepr='', offset=18, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='MAKE_FUNCTION', opcode=132, arg=9, argval=9, argrepr='defaults, closure', offset=22, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='inner', argrepr='inner', offset=24, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='NULL + print', offset=26, starts_line=5, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=3, argval='a', argrepr='a', offset=38, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=4, argval='b', argrepr='b', offset=40, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=0, argval='c', argrepr='c', offset=42, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=1, argval='d', argrepr='d', offset=44, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=4, argval=4, argrepr='', offset=46, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=4, argval=4, argrepr='', offset=50, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=60, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='inner', argrepr='inner', offset=62, starts_line=6, is_jump_target=False, positions=None),
|
|
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=64, starts_line=None, is_jump_target=False, positions=None),
|
|
]
|
|
|
|
expected_opinfo_inner = [
|
|
Instruction(opname='COPY_FREE_VARS', opcode=149, arg=4, argval=4, argrepr='', offset=0, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RESUME', opcode=151, arg=0, argval=0, argrepr='', offset=2, starts_line=3, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='NULL + print', offset=4, starts_line=4, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=2, argval='a', argrepr='a', offset=16, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=3, argval='b', argrepr='b', offset=18, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=4, argval='c', argrepr='c', offset=20, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_DEREF', opcode=137, arg=5, argval='d', argrepr='d', offset=22, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='e', argrepr='e', offset=24, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=1, argval='f', argrepr='f', offset=26, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=6, argval=6, argrepr='', offset=28, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=6, argval=6, argrepr='', offset=32, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=42, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=44, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=46, starts_line=None, is_jump_target=False, positions=None),
|
|
]
|
|
|
|
expected_opinfo_jumpy = [
|
|
Instruction(opname='RESUME', opcode=151, arg=0, argval=0, argrepr='', offset=0, starts_line=1, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='range', argrepr='NULL + range', offset=2, starts_line=3, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=10, argrepr='10', offset=14, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=16, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=20, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='GET_ITER', opcode=68, arg=None, argval=None, argrepr='', offset=30, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='FOR_ITER', opcode=93, arg=32, argval=98, argrepr='to 98', offset=32, starts_line=None, is_jump_target=True, positions=None),
|
|
Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=34, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=36, starts_line=4, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=48, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=50, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=54, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=64, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=66, starts_line=5, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=68, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='COMPARE_OP', opcode=107, arg=0, argval='<', argrepr='<', offset=70, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_JUMP_FORWARD_IF_FALSE', opcode=114, arg=1, argval=80, argrepr='to 80', offset=76, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='JUMP_BACKWARD', opcode=140, arg=24, argval=32, argrepr='to 32', offset=78, starts_line=6, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=80, starts_line=7, is_jump_target=True, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=82, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='COMPARE_OP', opcode=107, arg=4, argval='>', argrepr='>', offset=84, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_JUMP_FORWARD_IF_FALSE', opcode=114, arg=2, argval=96, argrepr='to 96', offset=90, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=92, starts_line=8, is_jump_target=False, positions=None),
|
|
Instruction(opname='JUMP_FORWARD', opcode=110, arg=16, argval=128, argrepr='to 128', offset=94, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='JUMP_BACKWARD', opcode=140, arg=33, argval=32, argrepr='to 32', offset=96, starts_line=7, is_jump_target=True, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=98, starts_line=10, is_jump_target=True, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=110, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=112, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=116, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=126, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=128, starts_line=11, is_jump_target=True, positions=None),
|
|
Instruction(opname='POP_JUMP_FORWARD_IF_FALSE', opcode=114, arg=36, argval=204, argrepr='to 204', offset=130, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=132, starts_line=12, is_jump_target=True, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=144, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=146, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=150, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=160, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=162, starts_line=13, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=164, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='BINARY_OP', opcode=122, arg=23, argval=23, argrepr='-=', offset=166, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=170, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=172, starts_line=14, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=174, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='COMPARE_OP', opcode=107, arg=4, argval='>', argrepr='>', offset=176, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_JUMP_FORWARD_IF_FALSE', opcode=114, arg=1, argval=186, argrepr='to 186', offset=182, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='JUMP_BACKWARD', opcode=140, arg=29, argval=128, argrepr='to 128', offset=184, starts_line=15, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=186, starts_line=16, is_jump_target=True, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=188, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='COMPARE_OP', opcode=107, arg=0, argval='<', argrepr='<', offset=190, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_JUMP_FORWARD_IF_FALSE', opcode=114, arg=1, argval=200, argrepr='to 200', offset=196, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='JUMP_FORWARD', opcode=110, arg=17, argval=234, argrepr='to 234', offset=198, starts_line=17, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=200, starts_line=11, is_jump_target=True, positions=None),
|
|
Instruction(opname='POP_JUMP_BACKWARD_IF_TRUE', opcode=176, arg=36, argval=132, argrepr='to 132', offset=202, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=204, starts_line=19, is_jump_target=True, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=216, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=218, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=222, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=232, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='NOP', opcode=9, arg=None, argval=None, argrepr='', offset=234, starts_line=20, is_jump_target=True, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=236, starts_line=21, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval=0, argrepr='0', offset=238, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='BINARY_OP', opcode=122, arg=11, argval=11, argrepr='/', offset=240, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=244, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=246, starts_line=25, is_jump_target=False, positions=None),
|
|
Instruction(opname='BEFORE_WITH', opcode=53, arg=None, argval=None, argrepr='', offset=248, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='STORE_FAST', opcode=125, arg=1, argval='dodgy', argrepr='dodgy', offset=250, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=252, starts_line=26, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval='Never reach this', argrepr="'Never reach this'", offset=264, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=266, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=270, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=280, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=282, starts_line=25, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=284, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=286, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=2, argval=2, argrepr='', offset=288, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=2, argval=2, argrepr='', offset=292, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=302, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='JUMP_FORWARD', opcode=110, arg=11, argval=328, argrepr='to 328', offset=304, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PUSH_EXC_INFO', opcode=35, arg=None, argval=None, argrepr='', offset=306, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='WITH_EXCEPT_START', opcode=49, arg=None, argval=None, argrepr='', offset=308, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_JUMP_FORWARD_IF_TRUE', opcode=115, arg=4, argval=320, argrepr='to 320', offset=310, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RERAISE', opcode=119, arg=2, argval=2, argrepr='', offset=312, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='COPY', opcode=120, arg=3, argval=3, argrepr='', offset=314, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=316, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RERAISE', opcode=119, arg=1, argval=1, argrepr='', offset=318, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=320, starts_line=None, is_jump_target=True, positions=None),
|
|
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=322, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=324, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=326, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='JUMP_FORWARD', opcode=110, arg=31, argval=392, argrepr='to 392', offset=328, starts_line=None, is_jump_target=True, positions=None),
|
|
Instruction(opname='PUSH_EXC_INFO', opcode=35, arg=None, argval=None, argrepr='', offset=330, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=332, starts_line=22, is_jump_target=False, positions=None),
|
|
Instruction(opname='CHECK_EXC_MATCH', opcode=36, arg=None, argval=None, argrepr='', offset=344, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_JUMP_FORWARD_IF_FALSE', opcode=114, arg=18, argval=384, argrepr='to 384', offset=346, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=348, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=350, starts_line=23, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=9, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=362, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=364, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=368, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=378, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=380, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='JUMP_FORWARD', opcode=110, arg=4, argval=392, argrepr='to 392', offset=382, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RERAISE', opcode=119, arg=0, argval=0, argrepr='', offset=384, starts_line=22, is_jump_target=True, positions=None),
|
|
Instruction(opname='COPY', opcode=120, arg=3, argval=3, argrepr='', offset=386, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=388, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RERAISE', opcode=119, arg=1, argval=1, argrepr='', offset=390, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=392, starts_line=28, is_jump_target=True, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=404, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=406, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=410, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=420, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=422, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=424, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PUSH_EXC_INFO', opcode=35, arg=None, argval=None, argrepr='', offset=426, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=428, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=440, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='PRECALL', opcode=166, arg=1, argval=1, argrepr='', offset=442, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=446, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=456, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RERAISE', opcode=119, arg=0, argval=0, argrepr='', offset=458, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='COPY', opcode=120, arg=3, argval=3, argrepr='', offset=460, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=462, starts_line=None, is_jump_target=False, positions=None),
|
|
Instruction(opname='RERAISE', opcode=119, arg=1, argval=1, argrepr='', offset=464, starts_line=None, is_jump_target=False, positions=None),
|
|
]
|
|
|
|
# One last piece of inspect fodder to check the default line number handling
|
|
def simple(): pass
|
|
expected_opinfo_simple = [
|
|
Instruction(opname='RESUME', opcode=151, arg=0, argval=0, argrepr='', offset=0, starts_line=simple.__code__.co_firstlineno, is_jump_target=False, positions=None),
|
|
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=2, starts_line=None, is_jump_target=False),
|
|
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=4, starts_line=None, is_jump_target=False)
|
|
]
|
|
|
|
|
|
class InstructionTestCase(BytecodeTestCase):
|
|
|
|
def assertInstructionsEqual(self, instrs_1, instrs_2, /):
|
|
instrs_1 = [instr_1._replace(positions=None) for instr_1 in instrs_1]
|
|
instrs_2 = [instr_2._replace(positions=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_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)', '<test>', '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),
|
|
(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', '<test>', '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 = [
|
|
instruction.positions
|
|
for instruction in dis.get_instructions(
|
|
code, adaptive=adaptive, show_caches=show_caches
|
|
)
|
|
]
|
|
self.assertEqual(co_positions, dis_positions)
|
|
|
|
# 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(self.strip_offsets(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", "<string>", "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", "<string>", "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", "<string>", "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, "<string>", "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, "<string>", "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))
|
|
|
|
|
|
class TestDisTraceback(DisTestBase):
|
|
def setUp(self) -> None:
|
|
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()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|