bpo-45152: refactor the dis module to make handling of hasconst opcodes more generic (GH-28258)

This commit is contained in:
Irit Katriel 2021-09-15 10:14:15 +01:00 committed by GitHub
parent 1afc7b3219
commit 40d2ac92f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -26,6 +26,7 @@ FORMAT_VALUE_CONVERTERS = (
MAKE_FUNCTION = opmap['MAKE_FUNCTION']
MAKE_FUNCTION_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure')
LOAD_CONST = opmap['LOAD_CONST']
def _try_compile(source, name):
"""Attempts to compile the given source, first as an expression and
@ -317,19 +318,32 @@ def get_instructions(x, *, first_line=None):
co.co_names, co.co_consts,
linestarts, line_offset, co_positions=co.co_positions())
def _get_const_info(const_index, const_list):
def _get_const_value(op, arg, co_consts):
"""Helper to get the value of the const in a hasconst op.
Returns the dereferenced constant if this is possible.
Otherwise (if it is a LOAD_CONST and co_consts is not
provided) returns the dis.UNKNOWN sentinel.
"""
assert op in hasconst
argval = UNKNOWN
if op == LOAD_CONST:
if co_consts is not None:
argval = co_consts[arg]
return argval
def _get_const_info(op, arg, co_consts):
"""Helper to get optional details about const references
Returns the dereferenced constant and its repr if the constant
list is defined.
Returns the dereferenced constant and its repr if the value
can be calculated.
Otherwise returns the sentinel value dis.UNKNOWN for the value
and an empty string for its repr.
"""
if const_list is not None:
argval = const_list[const_index]
return argval, repr(argval)
else:
return UNKNOWN, ''
argval = _get_const_value(op, arg, co_consts)
argrepr = repr(argval) if argval is not UNKNOWN else ''
return argval, argrepr
def _get_name_info(name_index, get_name, **extrainfo):
"""Helper to get optional details about named references
@ -371,14 +385,14 @@ def parse_exception_table(code):
return entries
def _get_instructions_bytes(code, varname_from_oparg=None,
names=None, constants=None,
names=None, co_consts=None,
linestarts=None, line_offset=0,
exception_entries=(), co_positions=None):
"""Iterate over the instructions in a bytecode string.
Generates a sequence of Instruction namedtuples giving the details of each
opcode. Additional information about the code's runtime environment
(e.g. variable names, constants) can be specified using optional
(e.g. variable names, co_consts) can be specified using optional
arguments.
"""
@ -408,7 +422,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
# raw name index for LOAD_GLOBAL, LOAD_CONST, etc.
argval = arg
if op in hasconst:
argval, argrepr = _get_const_info(arg, constants)
argval, argrepr = _get_const_info(op, arg, co_consts)
elif op in hasname:
argval, argrepr = _get_name_info(arg, get_name)
elif op in hasjabs:
@ -457,7 +471,7 @@ def _disassemble_recursive(co, *, file=None, depth=None):
_disassemble_recursive(x, file=file, depth=depth)
def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
names=None, constants=None, linestarts=None,
names=None, co_consts=None, linestarts=None,
*, file=None, line_offset=0, exception_entries=(),
co_positions=None):
# Omit the line number column entirely if we have no line number info
@ -476,7 +490,7 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
else:
offset_width = 4
for instr in _get_instructions_bytes(code, varname_from_oparg, names,
constants, linestarts,
co_consts, linestarts,
line_offset=line_offset, exception_entries=exception_entries,
co_positions=co_positions):
new_source_line = (show_lineno and
@ -557,11 +571,13 @@ def _find_imports(co):
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
and opargs[i-1][0] == opargs[i-2][0] == LOAD_CONST):
level = consts[opargs[i-2][1]]
fromlist = consts[opargs[i-1][1]]
yield (names[oparg], level, fromlist)
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
@ -635,7 +651,7 @@ class Bytecode:
with io.StringIO() as output:
_disassemble_bytes(co.co_code,
varname_from_oparg=co._varname_from_oparg,
names=co.co_names, constants=co.co_consts,
names=co.co_names, co_consts=co.co_consts,
linestarts=self._linestarts,
line_offset=self._line_offset,
file=output,