Skip to content

Commit 26c0e5e

Browse files
gh-108082: Remove _PyErr_WriteUnraisableMsg() (GH-111643)
Replace the remaining calls with PyErr_FormatUnraisable().
1 parent 0d3df27 commit 26c0e5e

13 files changed

+87
-149
lines changed

Include/internal/pycore_pyerrors.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,6 @@ Py_DEPRECATED(3.12) extern void _PyErr_ChainExceptions(PyObject *, PyObject *, P
194194
// Export for '_zoneinfo' shared extension
195195
PyAPI_FUNC(void) _PyErr_ChainExceptions1(PyObject *);
196196

197-
// Export for '_lsprof' shared extension
198-
PyAPI_FUNC(void) _PyErr_WriteUnraisableMsg(
199-
const char *err_msg,
200-
PyObject *obj);
201-
202197
#ifdef __cplusplus
203198
}
204199
#endif

Lib/test/_test_atexit.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ def assert_raises_unraisable(self, exc_type, func, *args):
1919
atexit.register(func, *args)
2020
atexit._run_exitfuncs()
2121

22-
self.assertEqual(cm.unraisable.object, func)
22+
self.assertIsNone(cm.unraisable.object)
23+
self.assertEqual(cm.unraisable.err_msg,
24+
f'Exception ignored in atexit callback {func!r}')
2325
self.assertEqual(cm.unraisable.exc_type, exc_type)
2426
self.assertEqual(type(cm.unraisable.exc_value), exc_type)
2527

@@ -125,7 +127,9 @@ def func():
125127
try:
126128
with support.catch_unraisable_exception() as cm:
127129
atexit._run_exitfuncs()
128-
self.assertEqual(cm.unraisable.object, func)
130+
self.assertIsNone(cm.unraisable.object)
131+
self.assertEqual(cm.unraisable.err_msg,
132+
f'Exception ignored in atexit callback {func!r}')
129133
self.assertEqual(cm.unraisable.exc_type, ZeroDivisionError)
130134
self.assertEqual(type(cm.unraisable.exc_value), ZeroDivisionError)
131135
finally:

Lib/test/audit-tests.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ def hook(event, args):
289289

290290

291291
def test_unraisablehook():
292-
from _testinternalcapi import write_unraisable_exc
292+
from _testcapi import err_formatunraisable
293293

294294
def unraisablehook(hookargs):
295295
pass
@@ -302,7 +302,8 @@ def hook(event, args):
302302

303303
sys.addaudithook(hook)
304304
sys.unraisablehook = unraisablehook
305-
write_unraisable_exc(RuntimeError("nonfatal-error"), "for audit hook test", None)
305+
err_formatunraisable(RuntimeError("nonfatal-error"),
306+
"Exception ignored for audit hook test")
306307

307308

308309
def test_winreg():

Lib/test/test_ctypes/test_callbacks.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,9 +322,9 @@ def func():
322322

323323
self.assertIsInstance(cm.unraisable.exc_value, TypeError)
324324
self.assertEqual(cm.unraisable.err_msg,
325-
"Exception ignored on converting result "
326-
"of ctypes callback function")
327-
self.assertIs(cm.unraisable.object, func)
325+
f"Exception ignored on converting result "
326+
f"of ctypes callback function {func!r}")
327+
self.assertIsNone(cm.unraisable.object)
328328

329329

330330
if __name__ == '__main__':

Lib/test/test_ctypes/test_random_things.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ def expect_unraisable(self, exc_type, exc_msg=None):
5151
if exc_msg is not None:
5252
self.assertEqual(str(cm.unraisable.exc_value), exc_msg)
5353
self.assertEqual(cm.unraisable.err_msg,
54-
"Exception ignored on calling ctypes "
55-
"callback function")
56-
self.assertIs(cm.unraisable.object, callback_func)
54+
f"Exception ignored on calling ctypes "
55+
f"callback function {callback_func!r}")
56+
self.assertIsNone(cm.unraisable.object)
5757

5858
def test_ValueError(self):
5959
cb = CFUNCTYPE(c_int, c_int)(callback_func)

Lib/test/test_sys.py

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,38 +1215,38 @@ def test_disable_gil_abi(self):
12151215

12161216
@test.support.cpython_only
12171217
class UnraisableHookTest(unittest.TestCase):
1218-
def write_unraisable_exc(self, exc, err_msg, obj):
1219-
import _testinternalcapi
1220-
import types
1221-
err_msg2 = f"Exception ignored {err_msg}"
1222-
try:
1223-
_testinternalcapi.write_unraisable_exc(exc, err_msg, obj)
1224-
return types.SimpleNamespace(exc_type=type(exc),
1225-
exc_value=exc,
1226-
exc_traceback=exc.__traceback__,
1227-
err_msg=err_msg2,
1228-
object=obj)
1229-
finally:
1230-
# Explicitly break any reference cycle
1231-
exc = None
1232-
12331218
def test_original_unraisablehook(self):
1234-
for err_msg in (None, "original hook"):
1235-
with self.subTest(err_msg=err_msg):
1236-
obj = "an object"
1237-
1238-
with test.support.captured_output("stderr") as stderr:
1239-
with test.support.swap_attr(sys, 'unraisablehook',
1240-
sys.__unraisablehook__):
1241-
self.write_unraisable_exc(ValueError(42), err_msg, obj)
1242-
1243-
err = stderr.getvalue()
1244-
if err_msg is not None:
1245-
self.assertIn(f'Exception ignored {err_msg}: {obj!r}\n', err)
1246-
else:
1247-
self.assertIn(f'Exception ignored in: {obj!r}\n', err)
1248-
self.assertIn('Traceback (most recent call last):\n', err)
1249-
self.assertIn('ValueError: 42\n', err)
1219+
_testcapi = import_helper.import_module('_testcapi')
1220+
from _testcapi import err_writeunraisable, err_formatunraisable
1221+
obj = hex
1222+
1223+
with support.swap_attr(sys, 'unraisablehook',
1224+
sys.__unraisablehook__):
1225+
with support.captured_stderr() as stderr:
1226+
err_writeunraisable(ValueError(42), obj)
1227+
lines = stderr.getvalue().splitlines()
1228+
self.assertEqual(lines[0], f'Exception ignored in: {obj!r}')
1229+
self.assertEqual(lines[1], 'Traceback (most recent call last):')
1230+
self.assertEqual(lines[-1], 'ValueError: 42')
1231+
1232+
with support.captured_stderr() as stderr:
1233+
err_writeunraisable(ValueError(42), None)
1234+
lines = stderr.getvalue().splitlines()
1235+
self.assertEqual(lines[0], 'Traceback (most recent call last):')
1236+
self.assertEqual(lines[-1], 'ValueError: 42')
1237+
1238+
with support.captured_stderr() as stderr:
1239+
err_formatunraisable(ValueError(42), 'Error in %R', obj)
1240+
lines = stderr.getvalue().splitlines()
1241+
self.assertEqual(lines[0], f'Error in {obj!r}:')
1242+
self.assertEqual(lines[1], 'Traceback (most recent call last):')
1243+
self.assertEqual(lines[-1], 'ValueError: 42')
1244+
1245+
with support.captured_stderr() as stderr:
1246+
err_formatunraisable(ValueError(42), None)
1247+
lines = stderr.getvalue().splitlines()
1248+
self.assertEqual(lines[0], 'Traceback (most recent call last):')
1249+
self.assertEqual(lines[-1], 'ValueError: 42')
12501250

12511251
def test_original_unraisablehook_err(self):
12521252
# bpo-22836: PyErr_WriteUnraisable() should give sensible reports
@@ -1293,6 +1293,8 @@ def test_original_unraisablehook_exception_qualname(self):
12931293
# Check that the exception is printed with its qualified name
12941294
# rather than just classname, and the module names appears
12951295
# unless it is one of the hard-coded exclusions.
1296+
_testcapi = import_helper.import_module('_testcapi')
1297+
from _testcapi import err_writeunraisable
12961298
class A:
12971299
class B:
12981300
class X(Exception):
@@ -1304,9 +1306,7 @@ class X(Exception):
13041306
with test.support.captured_stderr() as stderr, test.support.swap_attr(
13051307
sys, 'unraisablehook', sys.__unraisablehook__
13061308
):
1307-
expected = self.write_unraisable_exc(
1308-
A.B.X(), "msg", "obj"
1309-
)
1309+
err_writeunraisable(A.B.X(), "obj")
13101310
report = stderr.getvalue()
13111311
self.assertIn(A.B.X.__qualname__, report)
13121312
if moduleName in ['builtins', '__main__']:
@@ -1322,34 +1322,45 @@ def test_original_unraisablehook_wrong_type(self):
13221322
sys.unraisablehook(exc)
13231323

13241324
def test_custom_unraisablehook(self):
1325+
_testcapi = import_helper.import_module('_testcapi')
1326+
from _testcapi import err_writeunraisable, err_formatunraisable
13251327
hook_args = None
13261328

13271329
def hook_func(args):
13281330
nonlocal hook_args
13291331
hook_args = args
13301332

1331-
obj = object()
1333+
obj = hex
13321334
try:
13331335
with test.support.swap_attr(sys, 'unraisablehook', hook_func):
1334-
expected = self.write_unraisable_exc(ValueError(42),
1335-
"custom hook", obj)
1336-
for attr in "exc_type exc_value exc_traceback err_msg object".split():
1337-
self.assertEqual(getattr(hook_args, attr),
1338-
getattr(expected, attr),
1339-
(hook_args, expected))
1336+
exc = ValueError(42)
1337+
err_writeunraisable(exc, obj)
1338+
self.assertIs(hook_args.exc_type, type(exc))
1339+
self.assertIs(hook_args.exc_value, exc)
1340+
self.assertIs(hook_args.exc_traceback, exc.__traceback__)
1341+
self.assertIsNone(hook_args.err_msg)
1342+
self.assertEqual(hook_args.object, obj)
1343+
1344+
err_formatunraisable(exc, "custom hook %R", obj)
1345+
self.assertIs(hook_args.exc_type, type(exc))
1346+
self.assertIs(hook_args.exc_value, exc)
1347+
self.assertIs(hook_args.exc_traceback, exc.__traceback__)
1348+
self.assertEqual(hook_args.err_msg, f'custom hook {obj!r}')
1349+
self.assertIsNone(hook_args.object)
13401350
finally:
13411351
# expected and hook_args contain an exception: break reference cycle
13421352
expected = None
13431353
hook_args = None
13441354

13451355
def test_custom_unraisablehook_fail(self):
1356+
_testcapi = import_helper.import_module('_testcapi')
1357+
from _testcapi import err_writeunraisable
13461358
def hook_func(*args):
13471359
raise Exception("hook_func failed")
13481360

13491361
with test.support.captured_output("stderr") as stderr:
13501362
with test.support.swap_attr(sys, 'unraisablehook', hook_func):
1351-
self.write_unraisable_exc(ValueError(42),
1352-
"custom hook fail", None)
1363+
err_writeunraisable(ValueError(42), "custom hook fail")
13531364

13541365
err = stderr.getvalue()
13551366
self.assertIn(f'Exception ignored in sys.unraisablehook: '

Lib/test/test_thread.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,9 @@ def task():
155155
started.acquire()
156156

157157
self.assertEqual(str(cm.unraisable.exc_value), "task failed")
158-
self.assertIs(cm.unraisable.object, task)
158+
self.assertIsNone(cm.unraisable.object)
159159
self.assertEqual(cm.unraisable.err_msg,
160-
"Exception ignored in thread started by")
160+
f"Exception ignored in thread started by {task!r}")
161161
self.assertIsNotNone(cm.unraisable.exc_traceback)
162162

163163

Modules/_ctypes/callbacks.c

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#endif
1010

1111
#include "pycore_call.h" // _PyObject_CallNoArgs()
12-
#include "pycore_pyerrors.h" // _PyErr_WriteUnraisableMsg()
1312
#include "pycore_runtime.h" // _Py_ID()
1413

1514
#include <stdbool.h>
@@ -216,8 +215,9 @@ static void _CallPythonObject(void *mem,
216215

217216
result = PyObject_Vectorcall(callable, args, nargs, NULL);
218217
if (result == NULL) {
219-
_PyErr_WriteUnraisableMsg("on calling ctypes callback function",
220-
callable);
218+
PyErr_FormatUnraisable(
219+
"Exception ignored on calling ctypes callback function %R",
220+
callable);
221221
}
222222

223223
#ifdef MS_WIN32
@@ -258,9 +258,10 @@ static void _CallPythonObject(void *mem,
258258

259259
if (keep == NULL) {
260260
/* Could not convert callback result. */
261-
_PyErr_WriteUnraisableMsg("on converting result "
262-
"of ctypes callback function",
263-
callable);
261+
PyErr_FormatUnraisable(
262+
"Exception ignored on converting result "
263+
"of ctypes callback function %R",
264+
callable);
264265
}
265266
else if (setfunc != _ctypes_get_fielddesc("O")->setfunc) {
266267
if (keep == Py_None) {
@@ -270,9 +271,10 @@ static void _CallPythonObject(void *mem,
270271
else if (PyErr_WarnEx(PyExc_RuntimeWarning,
271272
"memory leak in callback function.",
272273
1) == -1) {
273-
_PyErr_WriteUnraisableMsg("on converting result "
274-
"of ctypes callback function",
275-
callable);
274+
PyErr_FormatUnraisable(
275+
"Exception ignored on converting result "
276+
"of ctypes callback function %R",
277+
callable);
276278
}
277279
}
278280
}

Modules/_testinternalcapi.c

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,37 +1518,6 @@ restore_crossinterp_data(PyObject *self, PyObject *args)
15181518
}
15191519

15201520

1521-
/*[clinic input]
1522-
_testinternalcapi.write_unraisable_exc
1523-
exception as exc: object
1524-
err_msg: object
1525-
obj: object
1526-
/
1527-
[clinic start generated code]*/
1528-
1529-
static PyObject *
1530-
_testinternalcapi_write_unraisable_exc_impl(PyObject *module, PyObject *exc,
1531-
PyObject *err_msg, PyObject *obj)
1532-
/*[clinic end generated code: output=a0f063cdd04aad83 input=274381b1a3fa5cd6]*/
1533-
{
1534-
1535-
const char *err_msg_utf8;
1536-
if (err_msg != Py_None) {
1537-
err_msg_utf8 = PyUnicode_AsUTF8(err_msg);
1538-
if (err_msg_utf8 == NULL) {
1539-
return NULL;
1540-
}
1541-
}
1542-
else {
1543-
err_msg_utf8 = NULL;
1544-
}
1545-
1546-
PyErr_SetObject((PyObject *)Py_TYPE(exc), exc);
1547-
_PyErr_WriteUnraisableMsg(err_msg_utf8, obj);
1548-
Py_RETURN_NONE;
1549-
}
1550-
1551-
15521521
static PyObject *
15531522
raiseTestError(const char* test_name, const char* msg)
15541523
{
@@ -1699,7 +1668,6 @@ static PyMethodDef module_functions[] = {
16991668
{"perf_trampoline_set_persist_after_fork", perf_trampoline_set_persist_after_fork, METH_VARARGS},
17001669
{"get_crossinterp_data", get_crossinterp_data, METH_VARARGS},
17011670
{"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
1702-
_TESTINTERNALCAPI_WRITE_UNRAISABLE_EXC_METHODDEF
17031671
_TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF
17041672
{NULL, NULL} /* sentinel */
17051673
};

Modules/_threadmodule.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#include "Python.h"
66
#include "pycore_interp.h" // _PyInterpreterState.threads.count
77
#include "pycore_moduleobject.h" // _PyModule_GetState()
8-
#include "pycore_pyerrors.h" // _PyErr_WriteUnraisableMsg()
98
#include "pycore_pylifecycle.h"
109
#include "pycore_pystate.h" // _PyThreadState_SetCurrent()
1110
#include "pycore_sysmodule.h" // _PySys_GetAttr()
@@ -1071,7 +1070,8 @@ thread_run(void *boot_raw)
10711070
/* SystemExit is ignored silently */
10721071
PyErr_Clear();
10731072
else {
1074-
_PyErr_WriteUnraisableMsg("in thread started by", boot->func);
1073+
PyErr_FormatUnraisable(
1074+
"Exception ignored in thread started by %R", boot->func);
10751075
}
10761076
}
10771077
else {

Modules/atexitmodule.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#include "pycore_atexit.h" // export _Py_AtExit()
1111
#include "pycore_initconfig.h" // _PyStatus_NO_MEMORY
1212
#include "pycore_interp.h" // PyInterpreterState.atexit
13-
#include "pycore_pyerrors.h" // _PyErr_WriteUnraisableMsg()
1413
#include "pycore_pystate.h" // _PyInterpreterState_GET
1514

1615
/* ===================================================================== */
@@ -137,7 +136,8 @@ atexit_callfuncs(struct atexit_state *state)
137136
PyObject* the_func = Py_NewRef(cb->func);
138137
PyObject *res = PyObject_Call(cb->func, cb->args, cb->kwargs);
139138
if (res == NULL) {
140-
_PyErr_WriteUnraisableMsg("in atexit callback", the_func);
139+
PyErr_FormatUnraisable(
140+
"Exception ignored in atexit callback %R", the_func);
141141
}
142142
else {
143143
Py_DECREF(res);

0 commit comments

Comments
 (0)