]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-93252: Fix error handling for failed Python calls (GH-94693)
authorBrandt Bucher <brandtbucher@microsoft.com>
Sat, 9 Jul 2022 01:52:26 +0000 (18:52 -0700)
committerGitHub <noreply@github.com>
Sat, 9 Jul 2022 01:52:26 +0000 (18:52 -0700)
Lib/test/test_call.py
Misc/NEWS.d/next/Core and Builtins/2022-07-08-11-44-45.gh-issue-93252.i2358c.rst [new file with mode: 0644]
Python/ceval.c

index 6936f093e3db1912e648a7eb00a544db8a1c3287..07355e8fa0616c09380fe824d85f9aab404ca02a 100644 (file)
@@ -26,6 +26,18 @@ class FunctionCalls(unittest.TestCase):
         self.assertIsInstance(res, dict)
         self.assertEqual(list(res.items()), expected)
 
+    def test_frames_are_popped_after_failed_calls(self):
+        # GH-93252: stuff blows up if we don't pop the new frame after
+        # recovering from failed calls:
+        def f():
+            pass
+        for _ in range(1000):
+            try:
+                f(None)
+            except TypeError:
+                pass
+        # BOOM!
+
 
 @cpython_only
 class CFunctionCallsErrorMessages(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-07-08-11-44-45.gh-issue-93252.i2358c.rst b/Misc/NEWS.d/next/Core and Builtins/2022-07-08-11-44-45.gh-issue-93252.i2358c.rst
new file mode 100644 (file)
index 0000000..1cc2d85
--- /dev/null
@@ -0,0 +1,2 @@
+Fix an issue that caused internal frames to outlive failed Python function
+calls, possibly resulting in memory leaks or hard interpreter crashes.
index 0176002432e8ddff3462421f0ac91114e53fe462..2a0ce63d435d7c3c5d9bfb52c8b477fd035c4c06 100644 (file)
@@ -6410,7 +6410,7 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, PyFunctionObject *func,
     }
     if (initialize_locals(tstate, func, localsarray, args, argcount, kwnames)) {
         assert(frame->owner != FRAME_OWNED_BY_GENERATOR);
-        _PyFrame_Clear(frame);
+        _PyEvalFrameClearAndPop(tstate, frame);
         return NULL;
     }
     return frame;
@@ -6432,6 +6432,10 @@ fail:
 static void
 _PyEvalFrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame * frame)
 {
+    // Make sure that this is, indeed, the top frame. We can't check this in
+    // _PyThreadState_PopFrame, since f_code is already cleared at that point:
+    assert((PyObject **)frame + frame->f_code->co_framesize ==
+           tstate->datastack_top);
     tstate->recursion_remaining--;
     assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
     assert(frame->owner == FRAME_OWNED_BY_THREAD);