diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index d3546119bc9ee0..04b826ed09144e 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1146,7 +1146,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 86b449158f00d0..0cd82c53aa932a 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -338,7 +338,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 60879e2774e707..357956a4b661b3 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_call_c_function_extra_flags(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 3bdc16437e2bc6..2c5af6afd17f00 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -3984,6 +3984,22 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + _PyStackRef tmp = self; + self = PyStackRef_NULL; + stack_pointer[-2] = self; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); + assert(stack_pointer == _PyFrame_GetStackPointer(frame)); + _PyFrame_StackPointerValidate(frame); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-3] = callable; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(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 31eaeab0d67841..48482091130900 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -5038,7 +5038,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 c5b2dfcf5f618f..78d97c36de4324 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -19083,6 +19083,22 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + _PyStackRef tmp = self; + self = PyStackRef_NULL; + stack_pointer[-2] = self; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); + assert(stack_pointer == _PyFrame_GetStackPointer(frame)); + _PyFrame_StackPointerValidate(frame); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-3] = callable; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } @@ -19125,7 +19141,21 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { - stack_pointer += 1; + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + _PyStackRef tmp = self; + self = PyStackRef_NULL; + stack_pointer[-1] = self; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); + assert(stack_pointer == _PyFrame_GetStackPointer(frame)); + _PyFrame_StackPointerValidate(frame); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-2] = callable; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); + stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); @@ -19171,8 +19201,21 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + _PyStackRef tmp = self; + self = PyStackRef_NULL; stack_pointer[0] = self; - stack_pointer += 2; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); + assert(stack_pointer == _PyFrame_GetStackPointer(frame)); + _PyFrame_StackPointerValidate(frame); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-1] = callable; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); + stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); @@ -19220,10 +19263,21 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + _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); + _PyFrame_StackPointerInvalidate(frame); + assert(stack_pointer == _PyFrame_GetStackPointer(frame)); + _PyFrame_StackPointerValidate(frame); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[0] = callable; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index dd0ce41e4b06b4..21f3a63fb715f8 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3984,6 +3984,22 @@ int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); if (err) { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + _PyStackRef tmp = self; + self = PyStackRef_NULL; + stack_pointer[-2] = self; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); + assert(stack_pointer == _PyFrame_GetStackPointer(frame)); + _PyFrame_StackPointerValidate(frame); + tmp = callable; + callable = PyStackRef_NULL; + stack_pointer[-3] = callable; + PyStackRef_CLOSE(tmp); + _PyFrame_StackPointerInvalidate(frame); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); JUMP_TO_LABEL(error); } c = callable;