Repository navigation
test_repl's test_no_memory: AssertionError: -6 not found in (1, 120) #118331
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error3.13only security fixesonly security fixes
on Apr 26, 2024 I am able to reproduce it locally when checking out #118283 (but not in main for whatever reason).
The bug I saw was due to
_PyObject_SetManagedDictcalling_PyDict_DetachFromObject, which sets an exception (due to the no-memory condition). The exception is not handled, which causes the fatal error in_Py_Dealloc.We should call
PyErr_WriteUnraisable()or further propagate up the error.- added a commit that references this issue
on Apr 29, 2024 @hugovk, both PRs are now merged. When you get a chance to merge
maininto your PR, would you please verify that everything is okay and close the issue?Passing now, thank you!
After the first run, 5 tests failed on "Windows (free-threading) / build and test (x86)" but passed when restarted.
https://github.com/python/cpython/actions/runs/8884999534/attempts/1
https://github.com/python/cpython/actions/runs/8884999534/job/24396359883
Reacted by Sam GrossIt looks like this wasn't fully fixed, I'm seeing failures on my PR: #130469
The test case seems to pass on main, but my PR triggers the failure again for whatever reason. On main, actually running the snippet from the test in the REPL results in an assertion failure, and a hang on 3.13 release builds:
Python 3.14.0a5+ (heads/main:1e4a4344af4, Feb 23 2025, 07:51:14) [GCC 14.2.1 20250207] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) ... Traceback (most recent call last): File "<python-input-0>", line 1, in <module> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) ~^~ ZeroDivisionError: division by zero python: Objects/object.c:2972: _Py_Dealloc: Assertion `tstate->current_frame == NULL || tstate->current_frame->stackpointer != NULL' failed.I think there are three separate issues on main that are being tickled. I haven't had a chance to investigate 3.13.
_PyList_FromStackRefStealOnSuccessis marked as non-escaping. A quick workaround here is to remove it from the non-escaping list.take_ownershipdoes not save and restore exceptions around the call to_PyFrame_GetFrameObject(). We should do that.list_deallocdoes not setob_itemtoNULLafter freeing the items array (only affects the free-threaded build). We should also do that.
1.
_PyList_FromStackRefStealOnSuccessFirst, I think the assertion failure that @ZeroIntensity reported on the default build is because
_PyList_FromStackRefStealOnSuccessis marked as non-escaping. We are attempting to allocate a new list (frame 7)(rr) bt #0 0x00007ffff7c8bdac in __pthread_kill_implementation () from /lib64/libc.so.6 #1 0x00007ffff7c3e886 in raise () from /lib64/libc.so.6 #2 0x00007ffff7c28833 in abort () from /lib64/libc.so.6 #3 0x00007ffff7c2875b in __assert_fail_base.cold () from /lib64/libc.so.6 #4 0x00007ffff7c375c6 in __assert_fail () from /lib64/libc.so.6 #5 0x000000000052d1f1 in _Py_Dealloc (op=op@entry=0x7ffff753e120) at ./Include/refcount.h:392 #6 0x00000000004f8c9c in Py_DECREF (op=0x7ffff753e120, lineno=262, filename=0x7cd6f7 "Objects/listobject.c") at ./Include/refcount.h:393 #7 PyList_New (size=size@entry=1) at Objects/listobject.c:262 #8 0x00000000004fbc4f in _PyList_FromStackRefStealOnSuccess (src=src@entry=0x7ffff7e1d588, n=n@entry=1) at Objects/listobject.c:3206 #9 0x000000000063f54e in _PyEval_EvalFrameDefault (tstate=tstate@entry=0xa5c950 <_PyRuntime+329808>, frame=0x7ffff7e1d4b0,Allocating the list object succeeds because it is handled by the free list. Allocating the items array fails, and we decref the the list object that was returned from the free list:
Lines 259 to 263 in 1e4a434
op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *)); #endif if (op->ob_item == NULL) { Py_DECREF(op); return PyErr_NoMemory(); The decref destroys the last reference, calls
_Py_Dealloc, and we end up hitting the assertion:
Lines 2970 to 2972 in 1e4a434
/* This assertion doesn't hold for the free-threading build, as * PyStackRef_CLOSE_SPECIALIZED is not implemented */ assert(tstate->current_frame == NULL || tstate->current_frame->stackpointer != NULL); Since
_PyList_FromStackRefStealOnSuccessis marked as non-escaping, the interpreter generator doesn't generate any code to set and clear the stack pointer around the call:cpython/Python/generated_cases.c.h
Lines 1017 to 1019 in 1e4a434
values = &stack_pointer[-oparg]; PyObject *list_o = _PyList_FromStackRefStealOnSuccess(values, oparg); if (list_o == NULL) { I'm not sure what the right solution is. I think
_PyList_FromStackRefStealOnSuccessis correctly marked as non-escaping because the decref can't execute arbitrary code and it's the only decref that can be performed. Removing it from the list of non-escaping calls gets us past the assertion and probably isn't too bad for performance. I suspect that there are probably other functions that are marked as non-escaping with similar properties as_PyList_FromStackRefStealOnSuccessunder low memory scenarios though and it doesn't seem great to have to pessimize those as well.2.
take_ownershipAfter working around the above, we hit another assertion that I believe is caused by unconditionally clearing
MemoryErrors intake_ownership.Python 3.14.0a5+ (tags/v3.14.0a5-162-g1e4a4344af4-dirty:1e4a4344af4, Feb 23 2025, 13:18:39) [GCC 11.5.0 20240719 (Red Hat 11.5.0-2)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) Traceback (most recent call last): File "<python-input-0>", line 1, in <module> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) ~^~ ZeroDivisionError: division by zero python: Python/generated_cases.c.h:11990: _PyEval_EvalFrameDefault: Assertion `_PyErr_Occurred(tstate)' failed. Program received signal SIGABRT, Aborted.We're triggering this assertion:
cpython/Python/generated_cases.c.h
Lines 11987 to 11989 in 1e4a434
#else assert(_PyErr_Occurred(tstate)); #endif because no exception is set:
(rr) frame 5 #5 0x0000000000667db3 in _PyEval_EvalFrameDefault (tstate=tstate@entry=0xa5c950 <_PyRuntime+329808>, frame=0x7fd7f802f400, frame@entry=0x7fd7f802f1d0, throwflag=throwflag@entry=0) at Python/generated_cases.c.h:11970 11970 JUMP_TO_LABEL(error); (rr) p tstate->current_exception $2 = (PyObject *) 0x0 (rr)Running backwards, we can see that the exception is cleared in
take_ownership:(rr) watch *(&tstate->current_exception) Hardware watchpoint 1: *(&tstate->current_exception) (rr) reverse-continue Continuing. Program received signal SIGABRT, Aborted. 0x00007fd7f7e8bdac in __pthread_kill_implementation () from /lib64/libc.so.6 (rr) reverse-continue Continuing. Hardware watchpoint 1: *(&tstate->current_exception) Old value = (PyObject *) 0x0 New value = (PyObject *) 0x7fd7f7d9ba80 _PyErr_SetRaisedException (tstate=0xa5c950 <_PyRuntime+329808>, exc=exc@entry=0x0) at Python/errors.c:29 29 tstate->current_exception = exc; (rr) bt #0 _PyErr_SetRaisedException (tstate=0xa5c950 <_PyRuntime+329808>, exc=exc@entry=0x0) at Python/errors.c:29 #1 0x000000000068f2fe in _PyErr_Restore (tstate=0xa5c950 <_PyRuntime+329808>, type=type@entry=0x0, value=value@entry=0x0, traceback=traceback@entry=0x0) at Python/errors.c:66 #2 0x000000000068f59f in _PyErr_Clear (tstate=<optimized out>) at Python/errors.c:535 #3 0x000000000068f67e in PyErr_Clear () at Python/errors.c:543 #4 0x000000000069a411 in take_ownership (f=f@entry=0x7fd7f787eb70, frame=0x7fd7f787ebb0, frame@entry=0x7fd7f802f4b0) at Python/frame.c:79 #5 0x000000000069a720 in _PyFrame_ClearExceptCode (frame=frame@entry=0x7fd7f802f4b0) at Python/frame.c:118 #6 0x0000000000635346 in clear_thread_frame (tstate=tstate@entry=0xa5c950 <_PyRuntime+329808>, frame=frame@entry=0x7fd7f802f4b0) at Python/ceval.c:1628 #7 0x0000000000638804 in _PyEval_FrameClearAndPop (tstate=tstate@entry=0xa5c950 <_PyRuntime+329808>, frame=frame@entry=0x7fd7f802f4b0) at Python/ceval.c:1652 #8 0x000000000063c21f in _PyEval_EvalFrameDefault (tstate=tstate@entry=0xa5c950 <_PyRuntime+329808>, frame=0x7fd7f802f400, frame@entry=0x7fd7f802f1d0, throwflag=throwflag@entry=0) at Python/generated_cases.c.h:12077take_ownershipclears anyMemoryErrorexceptions when getting the previous frame fails (even if they were not raised by the call to_PyFrame_GetFrameObject):Lines 74 to 79 in 1e4a434
PyFrameObject *back = _PyFrame_GetFrameObject(prev); if (back == NULL) { /* Memory error here. */ assert(PyErr_ExceptionMatches(PyExc_MemoryError)); /* Nothing we can do about it */ PyErr_Clear(); In this case, there was a pre-existing
MemoryError(caused by failing to allocate the list) that shouldn't be cleared. I think we should save and restore the exception around the call to_PyFrame_GetFrameObject(prev);. Something like:PyObject *exc = PyErr_GetRaisedException(); PyFrameObject *back = _PyFrame_GetFrameObject(prev); if (back == NULL) { /* Memory error here. */ assert(PyErr_ExceptionMatches(PyExc_MemoryError)); /* Nothing we can do about it */ PyErr_Clear(); } else { f->f_back = (PyFrameObject *)Py_NewRef(back); } PyErr_SetRaisedException(exc);
After this, it looks like things are working again on the default build:
> ./python Python 3.14.0a5+ (tags/v3.14.0a5-162-g1e4a4344af4-dirty:1e4a4344af4, Feb 23 2025, 14:10:04) [GCC 11.5.0 20240719 (Red Hat 11.5.0-2)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) Traceback (most recent call last): File "<python-input-0>", line 1, in <module> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) ~^~ ZeroDivisionError: division by zero object address : 0x7fc758b9ba80 object refcount : 3 object type : 0x9d6060 object type name: MemoryError object repr : lost sys.stderr >3.
list_deallocIt looks like there's another issue in the free-threaded build that we hit:
(rr) c Continuing. Python 3.14.0a5+ experimental free-threading build (tags/v3.14.0a5-162-g1e4a4344af4-dirty:1e4a4344af4, Feb 23 2025, 14:14:18) [GCC 11.5.0 20240719 (Red Hat 11.5.0-2)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) Traceback (most recent call last): File "<python-input-0>", line 1, in <module> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) ~^~ ZeroDivisionError: division by zero Program received signal SIGSEGV, Segmentation fault. Py_XDECREF (op=0xdddddddddddddddd) at ./Include/refcount.h:502 502 Py_DECREF(op); (rr) bt #0 Py_XDECREF (op=0xdddddddddddddddd) at ./Include/refcount.h:502 #1 list_dealloc (self=0x200008ebcb0) at Objects/listobject.c:533 #2 0x000000000053ec5c in _Py_Dealloc (op=0x200008ebcb0) at Objects/object.c:2986 #3 0x000000000053edd1 in _Py_MergeZeroLocalRefcount (op=op@entry=0x200008ebcb0) at Objects/object.c:436 #4 0x0000000000504e29 in Py_DECREF (op=0x200008ebcb0, lineno=253, filename=0x817777 "Objects/listobject.c") at ./Include/refcount.h:343 #5 PyList_New (size=size@entry=1) at Objects/listobject.c:253 #6 0x0000000000509b36 in _PyList_FromStackRefStealOnSuccess (src=src@entry=0x7f2791235588, n=n@entry=1) at Objects/listobject.c:3206 #7 0x000000000065de54 in _PyEval_EvalFrameDefault (tstate=tstate@entry=0xabef70 <_PyRuntime+360560>, frame=0x7f27912354b0, frame@entry=0x7f27912351d0, throwflag=throwflag@entry=0) at Python/generated_cases.c.h:1019Again, we're attempting to allocate a new list. Allocating the list object succeeds because it's returned from the free list, but allocating the items array fails:
Lines 250 to 255 in 1e4a434
#ifdef Py_GIL_DISABLED _PyListArray *array = list_allocate_array(size); if (array == NULL) { Py_DECREF(op); return PyErr_NoMemory(); } Since we hold the only reference, the decref ends up calling
list_dealloc, which is attempting to decref items in the items array, which has been filled with poisoned values since it was already freed:Lines 526 to 536 in 1e4a434
if (op->ob_item != NULL) { /* Do it backwards, for Christian Tismer. There's a simple test case where somehow this reduces thrashing when a *very* large list is created and immediately deleted. */ i = Py_SIZE(op); while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } free_list_items(op->ob_item, false); } I think setting setting
ob_itemtoNULLafter we free the items array should fix the problem. Something like:if (op->ob_item != NULL) { /* Do it backwards, for Christian Tismer. There's a simple test case where somehow this reduces thrashing when a *very* large list is created and immediately deleted. */ i = Py_SIZE(op); while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } free_list_items(op->ob_item, false); op->ob_item = NULL; }After this, the free-threaded build no longer crashes:
> ./python Python 3.14.0a5+ experimental free-threading build (tags/v3.14.0a5-162-g1e4a4344af4-dirty:1e4a4344af4, Feb 23 2025, 14:29:01) [GCC 11.5.0 20240719 (Red Hat 11.5.0-2)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) Traceback (most recent call last): File "<python-input-0>", line 1, in <module> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) ~^~ ZeroDivisionError: division by zero object address : 0x20000136b10 object refcount : 3 object type : 0xa2f520 object type name: MemoryError object repr : lost sys.stderr >Reacted by Peter Bierma- addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directoryinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)and removedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Mar 7, 2025 This seems to be fixed now:
Python 3.15.0a0 (heads/main:d8994b0a77, Jun 19 2025, 13:00:52) [GCC 14.2.1 20250207] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) Traceback (most recent call last): File "<python-input-0>", line 1, in <module> import sys, _testcapi; 1/0; _testcapi.set_nomemory(0); sys.exit(0) ~^~ ZeroDivisionError: division by zero
Bug report
Bug description:
One of my PRs (#118283) started failing (https://github.com/python/cpython/actions/runs/8843882336?pr=118283) with this. Investigating, I can reproduce it locally on
main, although I don't know why the CI passes onmain.OS: macOS Sonoma 14.4.1, M2.
To reproduce
Actual result
Printing
outputfrom the test:Expected result
It passes with
v3.13.0a5, whereoutputis:The main difference is the failure includes this:
Bisecting
PR: #117168
Issue: #111926
The PR was merged three weeks ago and the CI is passing.
On my PR (https://github.com/python/cpython/actions/runs/8843882336?pr=118283), free-threaded builds pass, but regular ones fail. Ubuntu and macOS fail with:
Windows with:
3221225477 seems to be 0xc0000005
STATUS_ACCESS_VIOLATION.CPython versions tested on:
3.13
Operating systems tested on:
macOS
Linked PRs
test_list.ListTest.test_no_memoryunder trace refs build #130921