Skip to content

Commit 733a5cf

Browse files
authored
[3.13] gh-143377: fix crashes in _interpreters.capture_exception (GH-143418) (#143653)
(cherry picked from commit ce6bae9)
1 parent 523d866 commit 733a5cf

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

Lib/test/test_interpreters/test_api.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44
from textwrap import dedent, indent
55
import threading
6+
import traceback
67
import types
78
import unittest
89

@@ -1667,6 +1668,55 @@ def test_set___main___attrs(self):
16671668
self.assertEqual(rc, 0)
16681669

16691670

1671+
class CaptureExceptionTests(unittest.TestCase):
1672+
1673+
# Prevent crashes with incompatible TracebackException.format().
1674+
# Regression test for https://github.com/python/cpython/issues/143377.
1675+
1676+
def capture_with_formatter(self, exc, formatter):
1677+
with support.swap_attr(traceback.TracebackException, "format", formatter):
1678+
return _interpreters.capture_exception(exc)
1679+
1680+
def test_capture_exception(self):
1681+
captured = _interpreters.capture_exception(ValueError("hello"))
1682+
1683+
self.assertEqual(captured.type.__name__, "ValueError")
1684+
self.assertEqual(captured.type.__qualname__, "ValueError")
1685+
self.assertEqual(captured.type.__module__, "builtins")
1686+
1687+
self.assertEqual(captured.msg, "hello")
1688+
self.assertEqual(captured.formatted, "ValueError: hello")
1689+
1690+
def test_capture_exception_custom_format(self):
1691+
exc = ValueError("good bye!")
1692+
formatter = lambda self: ["hello\n", "world\n"]
1693+
captured = self.capture_with_formatter(exc, formatter)
1694+
self.assertEqual(captured.msg, "good bye!")
1695+
self.assertEqual(captured.formatted, "ValueError: good bye!")
1696+
self.assertEqual(captured.errdisplay, "hello\nworld")
1697+
1698+
@support.subTests("exc_lines", ([], ["x-no-nl"], ["x-no-nl", "y-no-nl"]))
1699+
def test_capture_exception_invalid_format(self, exc_lines):
1700+
formatter = lambda self: exc_lines
1701+
captured = self.capture_with_formatter(ValueError(), formatter)
1702+
self.assertEqual(captured.msg, "")
1703+
self.assertEqual(captured.formatted, "ValueError: ")
1704+
self.assertEqual(captured.errdisplay, "".join(exc_lines))
1705+
1706+
@unittest.skipUnless(
1707+
support.Py_DEBUG,
1708+
"printing subinterpreter unraisable exceptions requires DEBUG build",
1709+
)
1710+
def test_capture_exception_unraisable_exception(self):
1711+
formatter = lambda self: 1
1712+
with support.catch_unraisable_exception() as cm:
1713+
captured = self.capture_with_formatter(ValueError(), formatter)
1714+
self.assertFalse(hasattr(captured, "errdisplay"))
1715+
self.assertEqual(cm.unraisable.exc_type, TypeError)
1716+
self.assertEqual(str(cm.unraisable.exc_value),
1717+
"can only join an iterable")
1718+
1719+
16701720
if __name__ == '__main__':
16711721
# Test needs to be a package, so we can do relative imports.
16721722
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash in :func:`!_interpreters.capture_exception` when
2+
the exception is incorrectly formatted. Patch by Bénédikt Tran.

Python/crossinterp.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data)
337337
/* convenience utilities */
338338
/*************************/
339339

340-
static const char *
340+
static char *
341341
_copy_string_obj_raw(PyObject *strobj, Py_ssize_t *p_size)
342342
{
343343
Py_ssize_t size = -1;
@@ -441,11 +441,16 @@ _format_TracebackException(PyObject *tbexc)
441441
}
442442

443443
Py_ssize_t size = -1;
444-
const char *formatted = _copy_string_obj_raw(formatted_obj, &size);
444+
char *formatted = _copy_string_obj_raw(formatted_obj, &size);
445445
Py_DECREF(formatted_obj);
446-
// We remove trailing the newline added by TracebackException.format().
447-
assert(formatted[size-1] == '\n');
448-
((char *)formatted)[size-1] = '\0';
446+
if (formatted == NULL || size == 0) {
447+
return formatted;
448+
}
449+
assert(formatted[size] == '\0');
450+
// Remove a trailing newline if needed.
451+
if (formatted[size-1] == '\n') {
452+
formatted[size-1] = '\0';
453+
}
449454
return formatted;
450455
}
451456

0 commit comments

Comments
 (0)