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