As of 3.11, code objects have a new co_positions()
method that yields tuples of the form (lineno_start, lineno_end, char_start, char_end)
for each bytecode instruction.
Combined with setting f_trace_opcodes
to True
on a frame, trace functions can theoretically track coverage on a character level.
There are a bunch of issues though, shown in part in the code:
- Some instructions correctly cover a wide range of code:
JUMP_*
instructions from branching code basically seems to span the entire length of the jump, i.e. from theif
to the end of the indented block.MAKE_FUNCTION
covers the entire function definition. These issues seem to be easy to resolve on first glance, because you can just ignore the corresponding opcodes. - Some instructions incorrectly (at least it seems that way to me) report an overly wide range. One example is a function with default values for parameters. These cause a
LOAD_CONST
instruction for a tuple of the default values, but the range extends for the entirety of the function. I have not found a good workaround for this.
One more small hack I've done is restrict almost every multiline opcode to the first line only. This allows me to not ignore some opcodes (like MAKE_FUNCTION
).
Overall verdict: Doing this correctly probably requires a) fixes in CPython to more accurately record bytecode ranges, and b) a whole lot of special casing, inspecting surrounding opcodes and opcode arguments, etc. to make it mostly work.