From f1a230bff9c6083b8ae39d7f4981e91ec046256e Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Sat, 4 Jul 2026 02:35:35 -0500 Subject: [PATCH] gh-150587: Guard specialized list-int subscript indices --- Lib/test/test_opcache.py | 26 +++++++++++++++++++ ...gh-issue-150587.list-int-compact-guard.rst | 3 +++ Modules/_testinternalcapi/test_cases.c.h | 10 +++++++ Python/bytecodes.c | 2 ++ Python/executor_cases.c.h | 15 +++++++++++ Python/generated_cases.c.h | 10 +++++++ 6 files changed, 66 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-07-04-02-30-00.gh-issue-150587.list-int-compact-guard.rst diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 7946550ec0db637..b5a19a728e4b1f7 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1879,6 +1879,19 @@ def binary_subscr_list_int(): "BINARY_OP_SUBSCR_LIST_INT") self.assert_no_opcode(binary_subscr_list_int, "BINARY_OP") + def binary_subscr_list_int_noncompact(index): + a = [1, 2, 3] + return a[index] + + for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): + self.assertEqual(binary_subscr_list_int_noncompact(0), 1) + self.assert_specialized(binary_subscr_list_int_noncompact, + "BINARY_OP_SUBSCR_LIST_INT") + # 2**30 + 2 is non-compact, but its low digit is 2. The specialized + # path must fall back instead of reading the low digit as the index. + with self.assertRaises(IndexError): + binary_subscr_list_int_noncompact(2**30 + 2) + def binary_subscr_tuple_int(): for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): a = (1, 2, 3) @@ -1995,6 +2008,19 @@ def __getitem__(self, item): @cpython_only @requires_specialization def test_store_subscr(self): + def store_subscr_list_int(index): + a = [None] + a[index] = 1 + return a + + for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): + self.assertEqual(store_subscr_list_int(0), [1]) + self.assert_specialized(store_subscr_list_int, "STORE_SUBSCR_LIST_INT") + # 2**30 + 2 is non-compact, but its low digit is 2. The specialized + # path must fall back instead of writing through the low digit index. + with self.assertRaises(IndexError): + store_subscr_list_int(2**30 + 2) + def store_subscr_dict(): for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): a = {1: 2, 2: 3} diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-04-02-30-00.gh-issue-150587.list-int-compact-guard.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-04-02-30-00.gh-issue-150587.list-int-compact-guard.rst new file mode 100644 index 000000000000000..eec9dbcc2e32aa0 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-04-02-30-00.gh-issue-150587.list-int-compact-guard.rst @@ -0,0 +1,3 @@ +Fix specialized list subscript and store-subscript paths so they fall back for +non-compact ``int`` indices instead of passing them to +``_PyLong_CompactValue()``. diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index 708aa4d966e5357..7635007a5b25dc2 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -852,6 +852,11 @@ PyObject *list = PyStackRef_AsPyObjectBorrow(list_st); assert(PyLong_CheckExact(sub)); assert(PyList_CheckExact(list)); + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) { + UPDATE_MISS_STATS(BINARY_OP); + assert(_PyOpcode_Deopt[opcode] == (BINARY_OP)); + JUMP_TO_PREDICTED(BINARY_OP); + } Py_ssize_t index = _PyLong_CompactValue((PyLongObject *)sub); if (index < 0) { index += PyList_GET_SIZE(list); @@ -12834,6 +12839,11 @@ PyObject *list = PyStackRef_AsPyObjectBorrow(list_st); assert(PyLong_CheckExact(sub)); assert(PyList_CheckExact(list)); + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) { + UPDATE_MISS_STATS(STORE_SUBSCR); + assert(_PyOpcode_Deopt[opcode] == (STORE_SUBSCR)); + JUMP_TO_PREDICTED(STORE_SUBSCR); + } Py_ssize_t index = _PyLong_CompactValue((PyLongObject *)sub); if (!LOCK_OBJECT(list)) { UPDATE_MISS_STATS(STORE_SUBSCR); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 6d5ae1b074db5b7..1e2dd07c5f63ed0 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1142,6 +1142,7 @@ dummy_func( assert(PyLong_CheckExact(sub)); assert(PyList_CheckExact(list)); + EXIT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)); Py_ssize_t index = _PyLong_CompactValue((PyLongObject *)sub); if (index < 0) { @@ -1420,6 +1421,7 @@ dummy_func( assert(PyLong_CheckExact(sub)); assert(PyList_CheckExact(list)); + DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)); Py_ssize_t index = _PyLong_CompactValue((PyLongObject *)sub); DEOPT_IF(!LOCK_OBJECT(list)); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 6be53213966678e..79654d5ad274a88 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -6911,6 +6911,13 @@ PyObject *list = PyStackRef_AsPyObjectBorrow(list_st); assert(PyLong_CheckExact(sub)); assert(PyList_CheckExact(list)); + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) { + UOP_STAT_INC(uopcode, miss); + _tos_cache1 = sub_st; + _tos_cache0 = list_st; + SET_CURRENT_CACHED_VALUES(2); + JUMP_TO_JUMP_TARGET(); + } Py_ssize_t index = _PyLong_CompactValue((PyLongObject *)sub); if (index < 0) { index += PyList_GET_SIZE(list); @@ -8359,6 +8366,14 @@ PyObject *list = PyStackRef_AsPyObjectBorrow(list_st); assert(PyLong_CheckExact(sub)); assert(PyList_CheckExact(list)); + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) { + UOP_STAT_INC(uopcode, miss); + _tos_cache2 = sub_st; + _tos_cache1 = list_st; + _tos_cache0 = value; + SET_CURRENT_CACHED_VALUES(3); + JUMP_TO_JUMP_TARGET(); + } Py_ssize_t index = _PyLong_CompactValue((PyLongObject *)sub); if (!LOCK_OBJECT(list)) { UOP_STAT_INC(uopcode, miss); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index d336b9fbcd57124..4172031992895de 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -852,6 +852,11 @@ PyObject *list = PyStackRef_AsPyObjectBorrow(list_st); assert(PyLong_CheckExact(sub)); assert(PyList_CheckExact(list)); + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) { + UPDATE_MISS_STATS(BINARY_OP); + assert(_PyOpcode_Deopt[opcode] == (BINARY_OP)); + JUMP_TO_PREDICTED(BINARY_OP); + } Py_ssize_t index = _PyLong_CompactValue((PyLongObject *)sub); if (index < 0) { index += PyList_GET_SIZE(list); @@ -12831,6 +12836,11 @@ PyObject *list = PyStackRef_AsPyObjectBorrow(list_st); assert(PyLong_CheckExact(sub)); assert(PyList_CheckExact(list)); + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) { + UPDATE_MISS_STATS(STORE_SUBSCR); + assert(_PyOpcode_Deopt[opcode] == (STORE_SUBSCR)); + JUMP_TO_PREDICTED(STORE_SUBSCR); + } Py_ssize_t index = _PyLong_CompactValue((PyLongObject *)sub); if (!LOCK_OBJECT(list)) { UPDATE_MISS_STATS(STORE_SUBSCR);