summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2023-10-31 21:42:44 (GMT)
committerGitHub <noreply@github.com>2023-10-31 21:42:44 (GMT)
commitf6a02327b5fcdc10df855985ca9d2d9dc2a0a46f (patch)
treebd0599c585b71b87092536ef0d8f381164283622
parent453e96e3020d38cfcaebf82b24cb681c6384fa82 (diff)
downloadcpython-f6a02327b5fcdc10df855985ca9d2d9dc2a0a46f.zip
cpython-f6a02327b5fcdc10df855985ca9d2d9dc2a0a46f.tar.gz
cpython-f6a02327b5fcdc10df855985ca9d2d9dc2a0a46f.tar.bz2
gh-108082: Add PyErr_FormatUnraisable() function (GH-111086)
-rw-r--r--Doc/c-api/exceptions.rst13
-rw-r--r--Doc/whatsnew/3.13.rst4
-rw-r--r--Include/cpython/pyerrors.h2
-rw-r--r--Lib/test/test_capi/test_exceptions.py57
-rw-r--r--Misc/NEWS.d/next/C API/2023-10-19-22-39-24.gh-issue-108082.uJytvc.rst1
-rw-r--r--Modules/_testcapi/exceptions.c25
-rw-r--r--Python/errors.c48
7 files changed, 142 insertions, 8 deletions
diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
index f27e2bb..a3a63b3 100644
--- a/Doc/c-api/exceptions.rst
+++ b/Doc/c-api/exceptions.rst
@@ -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
==================
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 1977902..9181685 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -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`.)
diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h
index da96eec..479b908 100644
--- a/Include/cpython/pyerrors.h
+++ b/Include/cpython/pyerrors.h
@@ -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))
diff --git a/Lib/test/test_capi/test_exceptions.py b/Lib/test/test_capi/test_exceptions.py
index 1bff65b..1d158e3 100644
--- a/Lib/test/test_capi/test_exceptions.py
+++ b/Lib/test/test_capi/test_exceptions.py
@@ -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):
diff --git a/Misc/NEWS.d/next/C API/2023-10-19-22-39-24.gh-issue-108082.uJytvc.rst b/Misc/NEWS.d/next/C API/2023-10-19-22-39-24.gh-issue-108082.uJytvc.rst
new file mode 100644
index 0000000..b99a829
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2023-10-19-22-39-24.gh-issue-108082.uJytvc.rst
@@ -0,0 +1 @@
+Add :c:func:`PyErr_FormatUnraisable` function.
diff --git a/Modules/_testcapi/exceptions.c b/Modules/_testcapi/exceptions.c
index aac672a..42a9915 100644
--- a/Modules/_testcapi/exceptions.c
+++ b/Modules/_testcapi/exceptions.c
@@ -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
diff --git a/Python/errors.c b/Python/errors.c
index 15af39b..f75c3e1 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -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);
}