diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 7d9babf358971d..c25d296d69a746 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1158,7 +1158,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [CALL_KW_NON_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_KW_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_SYNC_SP_FLAG | HAS_NEEDS_GUARD_IP_FLAG | HAS_RECORDS_VALUE_FLAG }, [CALL_LEN] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, - [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, + [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_METHOD_DESCRIPTOR_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG }, [CALL_METHOD_DESCRIPTOR_NOARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG }, diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 43bb21bd61f55c..52a20343f293a5 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -342,7 +342,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_GUARD_CALLABLE_ISINSTANCE] = HAS_EXIT_FLAG, [_CALL_ISINSTANCE] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG, [_GUARD_CALLABLE_LIST_APPEND] = HAS_EXIT_FLAG, - [_CALL_LIST_APPEND] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG, + [_CALL_LIST_APPEND] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG, [_GUARD_CALLABLE_METHOD_DESCRIPTOR_O] = HAS_ARG_FLAG | HAS_EXIT_FLAG, [_CALL_METHOD_DESCRIPTOR_O] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG, [_CHECK_RECURSION_LIMIT] = HAS_EXIT_FLAG, diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 7946550ec0db63..7bc8eea772c1a5 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -2,6 +2,7 @@ import copy import pickle import dis +import sys import threading import types import unittest @@ -2171,6 +2172,39 @@ class MyList(list): pass self.assert_no_opcode(my_list_append, "CALL_LIST_APPEND") self.assert_no_opcode(my_list_append, "CALL") + @cpython_only + @requires_specialization + def test_call_list_append_memory_error(self): + # gh-151818: if CALL_LIST_APPEND fails to resize the list, + # the argument must be released exactly once. + _testcapi = import_module("_testcapi") + + class Holder: + pass + + def list_append(l, holder): + # holder.item is pushed as a new reference, which + # CALL_LIST_APPEND steals. + l.append(holder.item) + + holder = Holder() + holder.item = item = object() + for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): + list_append([], holder) + self.assert_specialized(list_append, "CALL_LIST_APPEND") + + refcount = sys.getrefcount(item) + # An empty list has no spare capacity, so append() must resize it. + lst = [] + with self.assertRaises(MemoryError): + _testcapi.set_nomemory(0) + try: + list_append(lst, holder) + finally: + _testcapi.remove_mem_hooks() + self.assertEqual(lst, []) + self.assertEqual(sys.getrefcount(item), refcount) + @cpython_only @requires_specialization def test_load_attr_module_with_getattr(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-20-18-00-00.gh-issue-151818.LsApDf.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-20-18-00-00.gh-issue-151818.LsApDf.rst new file mode 100644 index 00000000000000..f1e56c81f2ff02 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-20-18-00-00.gh-issue-151818.LsApDf.rst @@ -0,0 +1,3 @@ +Fix a double-free crash in ``list.append()`` (the specialized +``CALL_LIST_APPEND`` instruction) when growing the list fails with +:exc:`MemoryError`. diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index 3e909c21e44044..8740595e428017 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -3812,6 +3812,18 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyStackRef tmp = self; + self = PyStackRef_NULL; + stack_pointer[-2] = self; + PyStackRef_CLOSE(tmp); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-3] = callable; + PyStackRef_CLOSE(tmp); + stack_pointer = _PyFrame_GetStackPointer(frame); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); JUMP_TO_LABEL(error); } c = callable; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 77aaa8a2f44626..ad400ec5e1fb45 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -5075,7 +5075,10 @@ dummy_func( int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { - ERROR_NO_POP(); + // arg is consumed even on failure: close the other inputs + // and pop them all, so that arg is not closed a second time. + DECREF_INPUTS(); + ERROR_IF(true); } c = callable; s = self; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 38da59a0f9bf3a..a76524a63f1c1e 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -19004,6 +19004,18 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyStackRef tmp = self; + self = PyStackRef_NULL; + stack_pointer[-2] = self; + PyStackRef_CLOSE(tmp); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-3] = callable; + PyStackRef_CLOSE(tmp); + stack_pointer = _PyFrame_GetStackPointer(frame); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } @@ -19046,7 +19058,17 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { - stack_pointer += 1; + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyStackRef tmp = self; + self = PyStackRef_NULL; + stack_pointer[-1] = self; + PyStackRef_CLOSE(tmp); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-2] = callable; + PyStackRef_CLOSE(tmp); + stack_pointer = _PyFrame_GetStackPointer(frame); + stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); @@ -19092,8 +19114,17 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyStackRef tmp = self; + self = PyStackRef_NULL; stack_pointer[0] = self; - stack_pointer += 2; + PyStackRef_CLOSE(tmp); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-1] = callable; + PyStackRef_CLOSE(tmp); + stack_pointer = _PyFrame_GetStackPointer(frame); + stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); @@ -19141,10 +19172,17 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyStackRef tmp = self; + self = PyStackRef_NULL; stack_pointer[0] = callable; stack_pointer[1] = self; - stack_pointer += 3; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + PyStackRef_CLOSE(tmp); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[0] = callable; + PyStackRef_CLOSE(tmp); + stack_pointer = _PyFrame_GetStackPointer(frame); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index d312fef398794f..a6d034a716d521 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3812,6 +3812,18 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyStackRef tmp = self; + self = PyStackRef_NULL; + stack_pointer[-2] = self; + PyStackRef_CLOSE(tmp); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-3] = callable; + PyStackRef_CLOSE(tmp); + stack_pointer = _PyFrame_GetStackPointer(frame); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); JUMP_TO_LABEL(error); } c = callable;