Skip to content

Commit 05d83a7

Browse files
authored
GH-91389: Fix dis position information for CACHEs (GH-93663) (GH-93921)
(cherry picked from commit f8e576b)
1 parent 1353b8a commit 05d83a7

File tree

3 files changed

+58
-12
lines changed

3 files changed

+58
-12
lines changed

Lib/dis.py

+22-10
Original file line numberDiff line numberDiff line change
@@ -492,17 +492,29 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
492492
yield Instruction(_all_opname[op], op,
493493
arg, argval, argrepr,
494494
offset, starts_line, is_jump_target, positions)
495-
if show_caches and _inline_cache_entries[deop]:
496-
for name, caches in _cache_format[opname[deop]].items():
497-
data = code[offset + 2: offset + 2 + caches * 2]
498-
argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}"
499-
for _ in range(caches):
500-
offset += 2
501-
yield Instruction(
502-
"CACHE", 0, 0, None, argrepr, offset, None, False, None
503-
)
504-
# Only show the actual value for the first cache entry:
495+
caches = _inline_cache_entries[deop]
496+
if not caches:
497+
continue
498+
if not show_caches:
499+
# We still need to advance the co_positions iterator:
500+
for _ in range(caches):
501+
next(co_positions, ())
502+
continue
503+
for name, size in _cache_format[opname[deop]].items():
504+
for i in range(size):
505+
offset += 2
506+
# Only show the fancy argrepr for a CACHE instruction when it's
507+
# the first entry for a particular cache value and the
508+
# instruction using it is actually quickened:
509+
if i == 0 and op != deop:
510+
data = code[offset: offset + 2 * size]
511+
argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}"
512+
else:
505513
argrepr = ""
514+
yield Instruction(
515+
"CACHE", CACHE, 0, None, argrepr, offset, None, False,
516+
Positions(*next(co_positions, ()))
517+
)
506518

507519
def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False):
508520
"""Disassemble a code object."""

Lib/test/test_dis.py

+34-2
Original file line numberDiff line numberDiff line change
@@ -1060,8 +1060,10 @@ def test_show_caches(self):
10601060
caches = list(self.get_cached_values(quickened, adaptive))
10611061
for cache in caches:
10621062
self.assertRegex(cache, pattern)
1063-
self.assertEqual(caches.count(""), 8)
1064-
self.assertEqual(len(caches), 25)
1063+
total_caches = 25
1064+
empty_caches = 8 if adaptive and quickened else total_caches
1065+
self.assertEqual(caches.count(""), empty_caches)
1066+
self.assertEqual(len(caches), total_caches)
10651067

10661068

10671069
class DisWithFileTests(DisTests):
@@ -1629,6 +1631,36 @@ def test_co_positions_missing_info(self):
16291631
self.assertIsNone(positions.col_offset)
16301632
self.assertIsNone(positions.end_col_offset)
16311633

1634+
@requires_debug_ranges()
1635+
def test_co_positions_with_lots_of_caches(self):
1636+
def roots(a, b, c):
1637+
d = b**2 - 4 * a * c
1638+
yield (-b - cmath.sqrt(d)) / (2 * a)
1639+
if d:
1640+
yield (-b + cmath.sqrt(d)) / (2 * a)
1641+
code = roots.__code__
1642+
ops = code.co_code[::2]
1643+
cache_opcode = opcode.opmap["CACHE"]
1644+
caches = sum(op == cache_opcode for op in ops)
1645+
non_caches = len(ops) - caches
1646+
# Make sure we have "lots of caches". If not, roots should be changed:
1647+
assert 1 / 3 <= caches / non_caches, "this test needs more caches!"
1648+
for show_caches in (False, True):
1649+
for adaptive in (False, True):
1650+
with self.subTest(f"{adaptive=}, {show_caches=}"):
1651+
co_positions = [
1652+
positions
1653+
for op, positions in zip(ops, code.co_positions(), strict=True)
1654+
if show_caches or op != cache_opcode
1655+
]
1656+
dis_positions = [
1657+
instruction.positions
1658+
for instruction in dis.get_instructions(
1659+
code, adaptive=adaptive, show_caches=show_caches
1660+
)
1661+
]
1662+
self.assertEqual(co_positions, dis_positions)
1663+
16321664
# get_instructions has its own tests above, so can rely on it to validate
16331665
# the object oriented API
16341666
class BytecodeTests(InstructionTestCase, DisTestBase):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix an issue where :mod:`dis` utilities could report missing or incorrect
2+
position information in the presence of ``CACHE`` entries.

0 commit comments

Comments
 (0)