mirror of https://github.com/python/cpython
gh-108082: Add PyErr_FormatUnraisable() function (GH-111086)
This commit is contained in:
parent
453e96e302
commit
f6a02327b5
|
@ -99,6 +99,18 @@ Printing and clearing
|
|||
Use :func:`sys.unraisablehook`.
|
||||
|
||||
|
||||
.. c:function:: void PyErr_FormatUnraisable(const char *format, ...)
|
||||
|
||||
Similar to :c:func:`PyErr_WriteUnraisable`, but the *format* and subsequent
|
||||
parameters help format the warning message; they have the same meaning and
|
||||
values as in :c:func:`PyUnicode_FromFormat`.
|
||||
``PyErr_WriteUnraisable(obj)`` is roughtly equivalent to
|
||||
``PyErr_FormatUnraisable("Exception ignored in: %R, obj)``.
|
||||
If *format* is ``NULL``, only the traceback is printed.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
.. c:function:: void PyErr_DisplayException(PyObject *exc)
|
||||
|
||||
Print the standard traceback display of ``exc`` to ``sys.stderr``, including
|
||||
|
@ -106,6 +118,7 @@ Printing and clearing
|
|||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
|
||||
Raising exceptions
|
||||
==================
|
||||
|
||||
|
|
|
@ -1107,6 +1107,10 @@ New Features
|
|||
limited C API.
|
||||
(Contributed by Victor Stinner in :gh:`85283`.)
|
||||
|
||||
* Add :c:func:`PyErr_FormatUnraisable` function: similar to
|
||||
:c:func:`PyErr_WriteUnraisable`, but allow to customize the warning mesage.
|
||||
(Contributed by Serhiy Storchaka in :gh:`108082`.)
|
||||
|
||||
* Add :c:func:`PyUnicode_AsUTF8` function to the limited C API.
|
||||
(Contributed by Victor Stinner in :gh:`111089`.)
|
||||
|
||||
|
|
|
@ -120,4 +120,6 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalErrorFunc(
|
|||
const char *func,
|
||||
const char *message);
|
||||
|
||||
PyAPI_FUNC(void) PyErr_FormatUnraisable(const char *, ...);
|
||||
|
||||
#define Py_FatalError(message) _Py_FatalErrorFunc(__func__, (message))
|
||||
|
|
|
@ -315,6 +315,63 @@ class Test_ErrSetAndRestore(unittest.TestCase):
|
|||
# CRASHES writeunraisable(NULL, hex)
|
||||
# CRASHES writeunraisable(NULL, NULL)
|
||||
|
||||
def test_err_formatunraisable(self):
|
||||
# Test PyErr_FormatUnraisable()
|
||||
formatunraisable = _testcapi.err_formatunraisable
|
||||
firstline = self.test_err_formatunraisable.__code__.co_firstlineno
|
||||
|
||||
with support.catch_unraisable_exception() as cm:
|
||||
formatunraisable(CustomError('oops!'), b'Error in %R', [])
|
||||
self.assertEqual(cm.unraisable.exc_type, CustomError)
|
||||
self.assertEqual(str(cm.unraisable.exc_value), 'oops!')
|
||||
self.assertEqual(cm.unraisable.exc_traceback.tb_lineno,
|
||||
firstline + 6)
|
||||
self.assertEqual(cm.unraisable.err_msg, 'Error in []')
|
||||
self.assertIsNone(cm.unraisable.object)
|
||||
|
||||
with support.catch_unraisable_exception() as cm:
|
||||
formatunraisable(CustomError('oops!'), b'undecodable \xff')
|
||||
self.assertEqual(cm.unraisable.exc_type, CustomError)
|
||||
self.assertEqual(str(cm.unraisable.exc_value), 'oops!')
|
||||
self.assertEqual(cm.unraisable.exc_traceback.tb_lineno,
|
||||
firstline + 15)
|
||||
self.assertIsNone(cm.unraisable.err_msg)
|
||||
self.assertIsNone(cm.unraisable.object)
|
||||
|
||||
with support.catch_unraisable_exception() as cm:
|
||||
formatunraisable(CustomError('oops!'), NULL)
|
||||
self.assertEqual(cm.unraisable.exc_type, CustomError)
|
||||
self.assertEqual(str(cm.unraisable.exc_value), 'oops!')
|
||||
self.assertEqual(cm.unraisable.exc_traceback.tb_lineno,
|
||||
firstline + 24)
|
||||
self.assertIsNone(cm.unraisable.err_msg)
|
||||
self.assertIsNone(cm.unraisable.object)
|
||||
|
||||
with (support.swap_attr(sys, 'unraisablehook', None),
|
||||
support.captured_stderr() as stderr):
|
||||
formatunraisable(CustomError('oops!'), b'Error in %R', [])
|
||||
lines = stderr.getvalue().splitlines()
|
||||
self.assertEqual(lines[0], f'Error in []:')
|
||||
self.assertEqual(lines[1], 'Traceback (most recent call last):')
|
||||
self.assertEqual(lines[-1], f'{__name__}.CustomError: oops!')
|
||||
|
||||
with (support.swap_attr(sys, 'unraisablehook', None),
|
||||
support.captured_stderr() as stderr):
|
||||
formatunraisable(CustomError('oops!'), b'undecodable \xff')
|
||||
lines = stderr.getvalue().splitlines()
|
||||
self.assertEqual(lines[0], 'Traceback (most recent call last):')
|
||||
self.assertEqual(lines[-1], f'{__name__}.CustomError: oops!')
|
||||
|
||||
with (support.swap_attr(sys, 'unraisablehook', None),
|
||||
support.captured_stderr() as stderr):
|
||||
formatunraisable(CustomError('oops!'), NULL)
|
||||
lines = stderr.getvalue().splitlines()
|
||||
self.assertEqual(lines[0], 'Traceback (most recent call last):')
|
||||
self.assertEqual(lines[-1], f'{__name__}.CustomError: oops!')
|
||||
|
||||
# CRASHES formatunraisable(NULL, b'Error in %R', [])
|
||||
# CRASHES formatunraisable(NULL, NULL)
|
||||
|
||||
|
||||
class Test_PyUnstable_Exc_PrepReraiseStar(ExceptionIsLikeMixin, unittest.TestCase):
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Add :c:func:`PyErr_FormatUnraisable` function.
|
|
@ -319,6 +319,30 @@ err_writeunraisable(PyObject *Py_UNUSED(module), PyObject *args)
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
err_formatunraisable(PyObject *Py_UNUSED(module), PyObject *args)
|
||||
{
|
||||
PyObject *exc;
|
||||
const char *fmt;
|
||||
Py_ssize_t fmtlen;
|
||||
PyObject *objs[10] = {NULL};
|
||||
|
||||
if (!PyArg_ParseTuple(args, "Oz#|OOOOOOOOOO", &exc, &fmt, &fmtlen,
|
||||
&objs[0], &objs[1], &objs[2], &objs[3], &objs[4],
|
||||
&objs[5], &objs[6], &objs[7], &objs[8], &objs[9]))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
NULLABLE(exc);
|
||||
if (exc) {
|
||||
PyErr_SetRaisedException(Py_NewRef(exc));
|
||||
}
|
||||
PyErr_FormatUnraisable(fmt,
|
||||
objs[0], objs[1], objs[2], objs[3], objs[4],
|
||||
objs[5], objs[6], objs[7], objs[8], objs[9]);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
_testcapi.unstable_exc_prep_reraise_star
|
||||
orig: object
|
||||
|
@ -364,6 +388,7 @@ static PyTypeObject PyRecursingInfinitelyError_Type = {
|
|||
static PyMethodDef test_methods[] = {
|
||||
{"err_restore", err_restore, METH_VARARGS},
|
||||
{"err_writeunraisable", err_writeunraisable, METH_VARARGS},
|
||||
{"err_formatunraisable", err_formatunraisable, METH_VARARGS},
|
||||
_TESTCAPI_ERR_SET_RAISED_METHODDEF
|
||||
_TESTCAPI_EXCEPTION_PRINT_METHODDEF
|
||||
_TESTCAPI_FATAL_ERROR_METHODDEF
|
||||
|
|
|
@ -1569,14 +1569,16 @@ _PyErr_WriteUnraisableDefaultHook(PyObject *args)
|
|||
for Python to handle it. For example, when a destructor raises an exception
|
||||
or during garbage collection (gc.collect()).
|
||||
|
||||
If err_msg_str is non-NULL, the error message is formatted as:
|
||||
"Exception ignored %s" % err_msg_str. Otherwise, use "Exception ignored in"
|
||||
error message.
|
||||
If format is non-NULL, the error message is formatted using format and
|
||||
variable arguments as in PyUnicode_FromFormat().
|
||||
Otherwise, use "Exception ignored in" error message.
|
||||
|
||||
An exception must be set when calling this function. */
|
||||
void
|
||||
_PyErr_WriteUnraisableMsg(const char *err_msg_str, PyObject *obj)
|
||||
|
||||
static void
|
||||
format_unraisable_v(const char *format, va_list va, PyObject *obj)
|
||||
{
|
||||
const char *err_msg_str;
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
_Py_EnsureTstateNotNULL(tstate);
|
||||
|
||||
|
@ -1610,8 +1612,8 @@ _PyErr_WriteUnraisableMsg(const char *err_msg_str, PyObject *obj)
|
|||
}
|
||||
}
|
||||
|
||||
if (err_msg_str != NULL) {
|
||||
err_msg = PyUnicode_FromFormat("Exception ignored %s", err_msg_str);
|
||||
if (format != NULL) {
|
||||
err_msg = PyUnicode_FromFormatV(format, va);
|
||||
if (err_msg == NULL) {
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
@ -1676,11 +1678,41 @@ done:
|
|||
_PyErr_Clear(tstate); /* Just in case */
|
||||
}
|
||||
|
||||
void
|
||||
PyErr_FormatUnraisable(const char *format, ...)
|
||||
{
|
||||
va_list va;
|
||||
|
||||
va_start(va, format);
|
||||
format_unraisable_v(format, va, NULL);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
static void
|
||||
format_unraisable(PyObject *obj, const char *format, ...)
|
||||
{
|
||||
va_list va;
|
||||
|
||||
va_start(va, format);
|
||||
format_unraisable_v(format, va, obj);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void
|
||||
_PyErr_WriteUnraisableMsg(const char *err_msg_str, PyObject *obj)
|
||||
{
|
||||
if (err_msg_str) {
|
||||
format_unraisable(obj, "Exception ignored %s", err_msg_str);
|
||||
}
|
||||
else {
|
||||
format_unraisable(obj, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PyErr_WriteUnraisable(PyObject *obj)
|
||||
{
|
||||
_PyErr_WriteUnraisableMsg(NULL, obj);
|
||||
format_unraisable(obj, NULL);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue