Summary
_BINARY_OP_SUBSCR_LIST_INT and _STORE_SUBSCR_LIST_INT call _PyLong_CompactValue
on the subscript without verifying compactness themselves. They rely entirely on
_GUARD_TOS_INT to have already rejected any non-compact int. This is an undocumented
invariant: if _GUARD_TOS_INT is ever relaxed, _PyLong_CompactValue will be called
on a wide int, reading ob_digit[0] as the index instead of the full value and
silently returning the wrong list element.
Details
The specializer gates BINARY_OP_SUBSCR_LIST_INT
(specialize.c:2391)
and STORE_SUBSCR_LIST_INT
(specialize.c:1575)
on _PyLong_IsNonNegativeCompact — only compact non-negative subscripts trigger
specialization. The runtime guard _GUARD_TOS_INT
(bytecodes.c:640)
currently checks _PyLong_CheckExactAndCompact, so the invariant holds today.
However, the opcode bodies
(_BINARY_OP_SUBSCR_LIST_INT,
_STORE_SUBSCR_LIST_INT)
do not re-verify compactness before calling _PyLong_CompactValue
(longintrepr.h:130).
If _GUARD_TOS_INT were relaxed to accept wider ints (e.g. to PyLong_CheckExact),
a wide int subscript would pass the guard and reach _PyLong_CompactValue in an
invalid state. For a wide positive int n = ob_digit[0] + ob_digit[1] * 2^30 + ...,
_PyLong_CompactValue reads only ob_digit[0], silently producing the wrong index.
On a debug build this fires assert(PyUnstable_Long_IsCompact(op)) inside
_PyLong_CompactValue.
Fix
Add EXIT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) to
_BINARY_OP_SUBSCR_LIST_INT and DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub))
to _STORE_SUBSCR_LIST_INT, immediately before the _PyLong_CompactValue call.
Each opcode then enforces its own precondition rather than relying on the guard's
current strength.
Linked PRs
Summary
_BINARY_OP_SUBSCR_LIST_INTand_STORE_SUBSCR_LIST_INTcall_PyLong_CompactValueon the subscript without verifying compactness themselves. They rely entirely on
_GUARD_TOS_INTto have already rejected any non-compact int. This is an undocumentedinvariant: if
_GUARD_TOS_INTis ever relaxed,_PyLong_CompactValuewill be calledon a wide int, reading
ob_digit[0]as the index instead of the full value andsilently returning the wrong list element.
Details
The specializer gates
BINARY_OP_SUBSCR_LIST_INT(specialize.c:2391)
and
STORE_SUBSCR_LIST_INT(specialize.c:1575)
on
_PyLong_IsNonNegativeCompact— only compact non-negative subscripts triggerspecialization. The runtime guard
_GUARD_TOS_INT(bytecodes.c:640)
currently checks
_PyLong_CheckExactAndCompact, so the invariant holds today.However, the opcode bodies
(
_BINARY_OP_SUBSCR_LIST_INT,_STORE_SUBSCR_LIST_INT)do not re-verify compactness before calling
_PyLong_CompactValue(longintrepr.h:130).
If
_GUARD_TOS_INTwere relaxed to accept wider ints (e.g. toPyLong_CheckExact),a wide int subscript would pass the guard and reach
_PyLong_CompactValuein aninvalid state. For a wide positive int
n = ob_digit[0] + ob_digit[1] * 2^30 + ...,_PyLong_CompactValuereads onlyob_digit[0], silently producing the wrong index.On a debug build this fires
assert(PyUnstable_Long_IsCompact(op))inside_PyLong_CompactValue.Fix
Add
EXIT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub))to_BINARY_OP_SUBSCR_LIST_INTandDEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub))to
_STORE_SUBSCR_LIST_INT, immediately before the_PyLong_CompactValuecall.Each opcode then enforces its own precondition rather than relying on the guard's
current strength.
Linked PRs