mirror of
https://github.com/python/cpython.git
synced 2025-09-09 18:32:22 +00:00
GH-91389: Fix dis position information for CACHEs (GH-93663)
This commit is contained in:
parent
4f85cec9e2
commit
f8e576be0a
3 changed files with 58 additions and 12 deletions
28
Lib/dis.py
28
Lib/dis.py
|
@ -497,17 +497,29 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
|
||||||
yield Instruction(_all_opname[op], op,
|
yield Instruction(_all_opname[op], op,
|
||||||
arg, argval, argrepr,
|
arg, argval, argrepr,
|
||||||
offset, starts_line, is_jump_target, positions)
|
offset, starts_line, is_jump_target, positions)
|
||||||
if show_caches and _inline_cache_entries[deop]:
|
caches = _inline_cache_entries[deop]
|
||||||
for name, caches in _cache_format[opname[deop]].items():
|
if not caches:
|
||||||
data = code[offset + 2: offset + 2 + caches * 2]
|
continue
|
||||||
argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}"
|
if not show_caches:
|
||||||
|
# We still need to advance the co_positions iterator:
|
||||||
for _ in range(caches):
|
for _ in range(caches):
|
||||||
|
next(co_positions, ())
|
||||||
|
continue
|
||||||
|
for name, size in _cache_format[opname[deop]].items():
|
||||||
|
for i in range(size):
|
||||||
offset += 2
|
offset += 2
|
||||||
yield Instruction(
|
# Only show the fancy argrepr for a CACHE instruction when it's
|
||||||
"CACHE", 0, 0, None, argrepr, offset, None, False, None
|
# the first entry for a particular cache value and the
|
||||||
)
|
# instruction using it is actually quickened:
|
||||||
# Only show the actual value for the first cache entry:
|
if i == 0 and op != deop:
|
||||||
|
data = code[offset: offset + 2 * size]
|
||||||
|
argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}"
|
||||||
|
else:
|
||||||
argrepr = ""
|
argrepr = ""
|
||||||
|
yield Instruction(
|
||||||
|
"CACHE", CACHE, 0, None, argrepr, offset, None, False,
|
||||||
|
Positions(*next(co_positions, ()))
|
||||||
|
)
|
||||||
|
|
||||||
def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
|
def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
|
||||||
"""Disassemble a code object."""
|
"""Disassemble a code object."""
|
||||||
|
|
|
@ -1197,8 +1197,10 @@ class DisTests(DisTestBase):
|
||||||
caches = list(self.get_cached_values(quickened, adaptive))
|
caches = list(self.get_cached_values(quickened, adaptive))
|
||||||
for cache in caches:
|
for cache in caches:
|
||||||
self.assertRegex(cache, pattern)
|
self.assertRegex(cache, pattern)
|
||||||
self.assertEqual(caches.count(""), 8)
|
total_caches = 22
|
||||||
self.assertEqual(len(caches), 22)
|
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):
|
class DisWithFileTests(DisTests):
|
||||||
|
@ -1751,6 +1753,36 @@ class InstructionTests(InstructionTestCase):
|
||||||
self.assertIsNone(positions.col_offset)
|
self.assertIsNone(positions.col_offset)
|
||||||
self.assertIsNone(positions.end_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
|
# get_instructions has its own tests above, so can rely on it to validate
|
||||||
# the object oriented API
|
# the object oriented API
|
||||||
class BytecodeTests(InstructionTestCase, DisTestBase):
|
class BytecodeTests(InstructionTestCase, DisTestBase):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix an issue where :mod:`dis` utilities could report missing or incorrect
|
||||||
|
position information in the presence of ``CACHE`` entries.
|
Loading…
Add table
Add a link
Reference in a new issue