]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-134163: Fix an infinite loop when the process runs out of memory in a ...
authoryihong <zouzou0208@gmail.com>
Wed, 10 Sep 2025 16:54:42 +0000 (00:54 +0800)
committerGitHub <noreply@github.com>
Wed, 10 Sep 2025 16:54:42 +0000 (12:54 -0400)
Signed-off-by: yihong0618 <zouzou0208@gmail.com>
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
Lib/test/test_exceptions.py
Misc/NEWS.d/next/Core_and_Builtins/2025-09-04-11-52-23.gh-issue-134163.EqKyn8.rst [new file with mode: 0644]
Python/ceval.c

index c91f66629483e569da41c62fd0108b083d4d6a91..939783956d62d6e1ab1e1b06f9731a3d49d8f400 100644 (file)
@@ -1843,6 +1843,38 @@ class ExceptionTests(unittest.TestCase):
         rc, _, err = script_helper.assert_python_ok("-c", code)
         self.assertIn(b'MemoryError', err)
 
+    @cpython_only
+    # Python built with Py_TRACE_REFS fail with a fatal error in
+    # _PyRefchain_Trace() on memory allocation error.
+    @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build')
+    def test_exec_set_nomemory_hang(self):
+        import_module("_testcapi")
+        # gh-134163: A MemoryError inside code that was wrapped by a try/except
+        # block would lead to an infinite loop.
+
+        # The frame_lasti needs to be greater than 257 to prevent
+        # PyLong_FromLong() from returning cached integers, which
+        # don't require a memory allocation. Prepend some dummy code
+        # to artificially increase the instruction index.
+        warmup_code = "a = list(range(0, 1))\n" * 20
+        user_input = warmup_code + dedent("""
+            try:
+                import _testcapi
+                _testcapi.set_nomemory(0)
+                b = list(range(1000, 2000))
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+            """)
+        with SuppressCrashReport():
+            with script_helper.spawn_python('-c', user_input) as p:
+                p.wait()
+                output = p.stdout.read()
+
+        self.assertIn(p.returncode, (0, 1))
+        self.assertGreater(len(output), 0)  # At minimum, should not hang
+        self.assertIn(b"MemoryError", output)
+
 
 class NameErrorTests(unittest.TestCase):
     def test_name_error_has_name(self):
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-04-11-52-23.gh-issue-134163.EqKyn8.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-04-11-52-23.gh-issue-134163.EqKyn8.rst
new file mode 100644 (file)
index 0000000..24e8efd
--- /dev/null
@@ -0,0 +1 @@
+Fix a hang when the process is out of memory inside an exception handler.
index 8c0cb29863cc60bd052ba2650a7a8e62fcd20a93..511e39f0c2e2ddd2746c3767823ef2af88e26f4d 100644 (file)
@@ -912,7 +912,11 @@ exception_unwind:
                 int frame_lasti = _PyInterpreterFrame_LASTI(frame);
                 PyObject *lasti = PyLong_FromLong(frame_lasti);
                 if (lasti == NULL) {
-                    goto exception_unwind;
+                    // Instead of going back to exception_unwind (which would cause
+                    // infinite recursion), directly exit to let the original exception
+                    // propagate up and hopefully be handled at a higher level.
+                    _PyFrame_SetStackPointer(frame, stack_pointer);
+                    goto exit_unwind;
                 }
                 PUSH(lasti);
             }