Skip to content

Commit a1512de

Browse files
committed
pythongh-143377: fix crashes in _interpreters.capture_exception
1 parent e6bfe4d commit a1512de

File tree

3 files changed

+58
-4
lines changed

3 files changed

+58
-4
lines changed

Lib/test/test_crossinterp.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import contextlib
22
import itertools
33
import sys
4+
import traceback
45
import types
56
import unittest
67
import warnings
78

8-
from test.support import import_helper
9+
from test import support
10+
from test.support import import_helper, swap_attr
911

1012
_testinternalcapi = import_helper.import_module('_testinternalcapi')
1113
_interpreters = import_helper.import_module('_interpreters')
@@ -1491,5 +1493,50 @@ def test_builtin_objects(self):
14911493
])
14921494

14931495

1496+
class CaptureExceptionTests(unittest.TestCase):
1497+
1498+
# Prevent crashes with incompatible TracebackException.format().
1499+
# Regression test for https://github.com/python/cpython/issues/143377.
1500+
1501+
def capture_with_formatter(self, exc, formatter):
1502+
with swap_attr(traceback.TracebackException, "format", formatter):
1503+
return _interpreters.capture_exception(exc)
1504+
1505+
def test_capture_exception(self):
1506+
captured = _interpreters.capture_exception(ValueError("hello"))
1507+
1508+
self.assertEqual(captured.type.__name__, "ValueError")
1509+
self.assertEqual(captured.type.__qualname__, "ValueError")
1510+
self.assertEqual(captured.type.__module__, "builtins")
1511+
1512+
self.assertEqual(captured.msg, "hello")
1513+
self.assertEqual(captured.formatted, "ValueError: hello")
1514+
1515+
def test_capture_exception_custom_format(self):
1516+
exc = ValueError("good bye!")
1517+
formatter = lambda self: ["hello\n", "world\n"]
1518+
captured = self.capture_with_formatter(exc, formatter)
1519+
self.assertEqual(captured.msg, "good bye!")
1520+
self.assertEqual(captured.formatted, "ValueError: good bye!")
1521+
self.assertEqual(captured.errdisplay, "hello\nworld")
1522+
1523+
@support.subTests("exc_lines", ([], ["x-no-nl"], ["x-no-nl", "y-no-nl"]))
1524+
def test_capture_exception_invalid_format(self, exc_lines):
1525+
formatter = lambda self: exc_lines
1526+
captured = self.capture_with_formatter(ValueError(), formatter)
1527+
self.assertEqual(captured.msg, "")
1528+
self.assertEqual(captured.formatted, "ValueError: ")
1529+
self.assertEqual(captured.errdisplay, "".join(exc_lines))
1530+
1531+
def test_capture_exception_unraisable_exception(self):
1532+
formatter = lambda self: 1
1533+
with support.catch_unraisable_exception() as cm:
1534+
captured = self.capture_with_formatter(ValueError(), formatter)
1535+
self.assertNotHasAttr(captured, "errdisplay")
1536+
self.assertEqual(cm.unraisable.exc_type, TypeError)
1537+
self.assertEqual(str(cm.unraisable.exc_value),
1538+
"can only join an iterable")
1539+
1540+
14941541
if __name__ == '__main__':
14951542
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: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,9 +1141,14 @@ _format_TracebackException(PyObject *tbexc)
11411141
Py_ssize_t size = -1;
11421142
const char *formatted = _copy_string_obj_raw(formatted_obj, &size);
11431143
Py_DECREF(formatted_obj);
1144-
// We remove trailing the newline added by TracebackException.format().
1145-
assert(formatted[size-1] == '\n');
1146-
((char *)formatted)[size-1] = '\0';
1144+
if (formatted == NULL || size == 0) {
1145+
return formatted;
1146+
}
1147+
assert(formatted[size] == '\0');
1148+
// Remove a trailing newline if needed.
1149+
if (formatted[size-1] == '\n') {
1150+
((char *)formatted)[size-1] = '\0';
1151+
}
11471152
return formatted;
11481153
}
11491154

0 commit comments

Comments
 (0)