gh-151763: Fix OOM-0013 crash when the parser or compiler fails to allocate#151968
Conversation
… to allocate compile(), exec(), eval() and ast.parse() could return NULL without an exception set when an allocation failed, breaking the result/error contract and crashing on the assertion that checks it. Set the error indicator at the points that returned NULL silently: new_compiler(), the _PyPegen_run_parser() result/exception check, and the tokenizer-init helpers.
| { | ||
| void *res = _PyPegen_parse(p); | ||
| assert(p->level == 0); | ||
| if (res != NULL && PyErr_Occurred()) { |
There was a problem hiding this comment.
Do we have a way to test this? We can force the allocation failure with _testcapi.set_nomemory (there is precedent in test_exceptions.py) and assert that compile() raises MemoryError instead of crashing on the contract assert.
|
Thanks @tonghuaroot for the PR, and @pablogsal for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14, 3.15. |
|
GH-152836 is a backport of this pull request to the 3.15 branch. |
|
Sorry, @tonghuaroot and @pablogsal, I could not cleanly backport this to |
|
GH-152837 is a backport of this pull request to the 3.14 branch. |
|
GH-152840 is a backport of this pull request to the 3.13 branch. |
|
GH-152840 does the 3.13 backport. The conflict was only the |
This fixes OOM-0013 from the gh-151763 umbrella.
compile(),exec(),eval()andast.parse()can return NULL withoutsetting an exception when an allocation fails, breaking the result/error
contract every consumer relies on and crashing on the assertion that checks it
(for example the
(res != NULL) ^ (PyErr_Occurred())check reached from theevaluation loop, and
assert(!PyErr_Occurred())at the top of_PyAST_Validate).Three points returned NULL silently under a failed allocation:
new_compiler()returned NULL without callingPyErr_NoMemory()when itsinitial
PyMem_Callocfailed -- the dominantcompile()/exec()/eval()path.
_PyPegen_run_parser()could return a valid result while leaving a staleexception pending.
Setting the error indicator at each point makes a failed allocation surface as
MemoryErrorinstead of a NULL-without-exception crash.Reproduction (debug build,
_testcapi.set_nomemory)ast.parse(...)underset_nomemoryaborted with "a function returned NULLwithout setting an exception"; a clean
MemoryErroris raised after the fix.compile()/exec()/eval()underset_nomemoryaborted at theresult/error contract assertion; clean after the fix.
No test is added, following the convention for these OOM crash fixes.