Skip to content

Commit

Permalink
pythongh-129354: Use PyErr_FormatUnraisable() function
Browse files Browse the repository at this point in the history
Replace PyErr_WriteUnraisable() with PyErr_FormatUnraisable().
  • Loading branch information
vstinner committed Jan 27, 2025
1 parent 7ec1742 commit 8e8167d
Show file tree
Hide file tree
Showing 35 changed files with 213 additions and 137 deletions.
2 changes: 0 additions & 2 deletions Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
Expand Up @@ -2137,7 +2137,6 @@ async def func(): pass
support.gc_collect()

self.assertIn("was never awaited", str(cm.unraisable.exc_value))
self.assertEqual(repr(cm.unraisable.object), coro_repr)

def test_for_assign_raising_stop_async_iteration(self):
class BadTarget:
Expand Down Expand Up @@ -2414,7 +2413,6 @@ async def corofn():
del coro
support.gc_collect()

self.assertEqual(repr(cm.unraisable.object), coro_repr)
self.assertEqual(cm.unraisable.exc_type, ZeroDivisionError)

del warnings._warn_unawaited_coroutine
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1681,7 +1681,6 @@ def __del__(self):
del obj

gc_collect() # For PyPy or other GCs.
self.assertEqual(cm.unraisable.object, BrokenDel.__del__)
self.assertIsNotNone(cm.unraisable.exc_traceback)

def test_unhandled(self):
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2779,14 +2779,12 @@ def printsolution(self, x):
... l = Leaker()
... del l
...
... cm.unraisable.object == Leaker.__del__
... cm.unraisable.exc_type == RuntimeError
... str(cm.unraisable.exc_value) == "del failed"
... cm.unraisable.exc_traceback is not None
True
True
True
True
These refleak tests should perhaps be in a testfile of their own,
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_sqlite3/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def progress():
con.execute("select 1 union select 2 union select 3").fetchall()
self.assertEqual(action, 0, "progress handler was not cleared")

@with_tracebacks(ZeroDivisionError, name="bad_progress")
@with_tracebacks(ZeroDivisionError, msg_regex="bad_progress")
def test_error_in_progress_handler(self):
def bad_progress():
1 / 0
Expand All @@ -206,7 +206,7 @@ def bad_progress():
create table foo(a, b)
""")

@with_tracebacks(ZeroDivisionError, name="bad_progress")
@with_tracebacks(ZeroDivisionError, msg_regex="bad_progress")
def test_error_in_progress_handler_result(self):
class BadBool:
def __bool__(self):
Expand Down
20 changes: 10 additions & 10 deletions Lib/test/test_sqlite3/test_userfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,22 +254,22 @@ def test_func_return_nan(self):
cur.execute("select returnnan()")
self.assertIsNone(cur.fetchone()[0])

@with_tracebacks(ZeroDivisionError, name="func_raiseexception")
@with_tracebacks(ZeroDivisionError, msg_regex="func_raiseexception")
def test_func_exception(self):
cur = self.con.cursor()
with self.assertRaises(sqlite.OperationalError) as cm:
cur.execute("select raiseexception()")
cur.fetchone()
self.assertEqual(str(cm.exception), 'user-defined function raised exception')

@with_tracebacks(MemoryError, name="func_memoryerror")
@with_tracebacks(MemoryError, msg_regex="func_memoryerror")
def test_func_memory_error(self):
cur = self.con.cursor()
with self.assertRaises(MemoryError):
cur.execute("select memoryerror()")
cur.fetchone()

@with_tracebacks(OverflowError, name="func_overflowerror")
@with_tracebacks(OverflowError, msg_regex="func_overflowerror")
def test_func_overflow_error(self):
cur = self.con.cursor()
with self.assertRaises(sqlite.DataError):
Expand Down Expand Up @@ -389,7 +389,7 @@ def test_func_return_too_large_int(self):
with self.assertRaisesRegex(sqlite.DataError, msg):
cur.execute("select largeint()")

@with_tracebacks(UnicodeEncodeError, "surrogates not allowed", "chr")
@with_tracebacks(UnicodeEncodeError, "surrogates not allowed")
def test_func_return_text_with_surrogates(self):
cur = self.con.cursor()
self.con.create_function("pychr", 1, chr)
Expand Down Expand Up @@ -641,7 +641,7 @@ def test_aggr_error_on_create(self):
with self.assertRaises(sqlite.OperationalError):
self.con.create_function("bla", -100, AggrSum)

@with_tracebacks(AttributeError, name="AggrNoStep")
@with_tracebacks(AttributeError, msg_regex="AggrNoStep")
def test_aggr_no_step(self):
cur = self.con.cursor()
with self.assertRaises(sqlite.OperationalError) as cm:
Expand All @@ -656,23 +656,23 @@ def test_aggr_no_finalize(self):
cur.execute("select nofinalize(t) from test")
val = cur.fetchone()[0]

@with_tracebacks(ZeroDivisionError, name="AggrExceptionInInit")
@with_tracebacks(ZeroDivisionError, msg_regex="AggrExceptionInInit")
def test_aggr_exception_in_init(self):
cur = self.con.cursor()
with self.assertRaises(sqlite.OperationalError) as cm:
cur.execute("select excInit(t) from test")
val = cur.fetchone()[0]
self.assertEqual(str(cm.exception), "user-defined aggregate's '__init__' method raised error")

@with_tracebacks(ZeroDivisionError, name="AggrExceptionInStep")
@with_tracebacks(ZeroDivisionError, msg_regex="AggrExceptionInStep")
def test_aggr_exception_in_step(self):
cur = self.con.cursor()
with self.assertRaises(sqlite.OperationalError) as cm:
cur.execute("select excStep(t) from test")
val = cur.fetchone()[0]
self.assertEqual(str(cm.exception), "user-defined aggregate's 'step' method raised error")

@with_tracebacks(ZeroDivisionError, name="AggrExceptionInFinalize")
@with_tracebacks(ZeroDivisionError, msg_regex="AggrExceptionInFinalize")
def test_aggr_exception_in_finalize(self):
cur = self.con.cursor()
with self.assertRaises(sqlite.OperationalError) as cm:
Expand Down Expand Up @@ -822,11 +822,11 @@ def authorizer_cb(action, arg1, arg2, dbname, source):
raise ValueError
return sqlite.SQLITE_OK

@with_tracebacks(ValueError, name="authorizer_cb")
@with_tracebacks(ValueError, msg_regex="authorizer_cb")
def test_table_access(self):
super().test_table_access()

@with_tracebacks(ValueError, name="authorizer_cb")
@with_tracebacks(ValueError, msg_regex="authorizer_cb")
def test_column_access(self):
super().test_table_access()

Expand Down
16 changes: 10 additions & 6 deletions Lib/test/test_sqlite3/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ def cx_limit(cx, category=sqlite3.SQLITE_LIMIT_SQL_LENGTH, limit=128):
cx.setlimit(category, _prev)


def with_tracebacks(exc, regex="", name=""):
def with_tracebacks(exc, regex="", name="", msg_regex=""):
"""Convenience decorator for testing callback tracebacks."""
def decorator(func):
_regex = re.compile(regex) if regex else None
exc_regex = re.compile(regex) if regex else None
_msg_regex = re.compile(msg_regex) if msg_regex else None
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
with test.support.catch_unraisable_exception() as cm:
# First, run the test with traceback enabled.
with check_tracebacks(self, cm, exc, _regex, name):
with check_tracebacks(self, cm, exc, exc_regex, _msg_regex, name):
func(self, *args, **kwargs)

# Then run the test with traceback disabled.
Expand All @@ -40,7 +41,7 @@ def wrapper(self, *args, **kwargs):


@contextlib.contextmanager
def check_tracebacks(self, cm, exc, regex, obj_name):
def check_tracebacks(self, cm, exc, exc_regex, msg_regex, obj_name):
"""Convenience context manager for testing callback tracebacks."""
sqlite3.enable_callback_tracebacks(True)
try:
Expand All @@ -49,9 +50,12 @@ def check_tracebacks(self, cm, exc, regex, obj_name):
yield

self.assertEqual(cm.unraisable.exc_type, exc)
if regex:
if exc_regex:
msg = str(cm.unraisable.exc_value)
self.assertIsNotNone(regex.search(msg))
self.assertIsNotNone(exc_regex.search(msg), (exc_regex, msg))
if msg_regex:
msg = cm.unraisable.err_msg
self.assertIsNotNone(msg_regex.search(msg), (msg_regex, msg))
if obj_name:
self.assertEqual(cm.unraisable.object.__name__, obj_name)
finally:
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ def __del__(self):
rc, stdout, stderr = assert_python_ok("-c", code)
self.assertEqual(rc, 0)
self.assertEqual(stdout.rstrip(), b"")
self.assertIn(b"Exception ignored in:", stderr)
self.assertIn(b"Exception ignored on calling deallocator", stderr)
self.assertIn(b"C.__del__", stderr)

def test__struct_reference_cycle_cleaned_up(self):
Expand Down
6 changes: 4 additions & 2 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1715,7 +1715,8 @@ FutureObj_finalize(FutureObj *fut)
if (func != NULL) {
PyObject *res = PyObject_CallOneArg(func, context);
if (res == NULL) {
PyErr_WriteUnraisable(func);
PyErr_FormatUnraisable("Exception ignored on calling asyncio "
"function %R", func);
}
else {
Py_DECREF(res);
Expand Down Expand Up @@ -2978,7 +2979,8 @@ TaskObj_finalize(TaskObj *task)
if (func != NULL) {
PyObject *res = PyObject_CallOneArg(func, context);
if (res == NULL) {
PyErr_WriteUnraisable(func);
PyErr_FormatUnraisable("Exception ignored on calling asyncio "
"function %R", func);
}
else {
Py_DECREF(res);
Expand Down
7 changes: 4 additions & 3 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,8 @@ CType_Type_traverse(PyObject *self, visitproc visit, void *arg)
{
StgInfo *info = _PyStgInfo_FromType_NoState(self);
if (!info) {
PyErr_WriteUnraisable(self);
PyErr_FormatUnraisable("Exception ignored on calling ctypes %R "
"traverse function", self);
}
if (info) {
Py_VISIT(info->proto);
Expand Down Expand Up @@ -494,7 +495,7 @@ CType_Type_clear(PyObject *self)
{
StgInfo *info = _PyStgInfo_FromType_NoState(self);
if (!info) {
PyErr_WriteUnraisable(self);
PyErr_FormatUnraisable("Exception ignored on clearing %R", self);
}
if (info) {
ctype_clear_stginfo(info);
Expand All @@ -507,7 +508,7 @@ CType_Type_dealloc(PyObject *self)
{
StgInfo *info = _PyStgInfo_FromType_NoState(self);
if (!info) {
PyErr_WriteUnraisable(NULL); // NULL avoids segfault here
PyErr_FormatUnraisable("Exception ignored on deallocating %R", self);
}
if (info) {
PyMem_Free(info->ffi_type_pointer.elements);
Expand Down
45 changes: 22 additions & 23 deletions Modules/_ctypes/callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -494,32 +494,28 @@ long Call_GetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)

func = _PyImport_GetModuleAttrString("ctypes", "DllGetClassObject");
if (!func) {
PyErr_WriteUnraisable(context ? context : Py_None);
/* There has been a warning before about this already */
return E_FAIL;
goto error;
}

{
PyObject *py_rclsid = PyLong_FromVoidPtr((void *)rclsid);
if (py_rclsid == NULL) {
Py_DECREF(func);
PyErr_WriteUnraisable(context ? context : Py_None);
return E_FAIL;
goto error;
}
PyObject *py_riid = PyLong_FromVoidPtr((void *)riid);
if (py_riid == NULL) {
Py_DECREF(func);
Py_DECREF(py_rclsid);
PyErr_WriteUnraisable(context ? context : Py_None);
return E_FAIL;
goto error;
}
PyObject *py_ppv = PyLong_FromVoidPtr(ppv);
if (py_ppv == NULL) {
Py_DECREF(py_rclsid);
Py_DECREF(py_riid);
Py_DECREF(func);
PyErr_WriteUnraisable(context ? context : Py_None);
return E_FAIL;
goto error;
}
result = PyObject_CallFunctionObjArgs(func,
py_rclsid,
Expand All @@ -532,17 +528,21 @@ long Call_GetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
}
Py_DECREF(func);
if (!result) {
PyErr_WriteUnraisable(context ? context : Py_None);
return E_FAIL;
goto error;
}

retval = PyLong_AsLong(result);
if (PyErr_Occurred()) {
PyErr_WriteUnraisable(context ? context : Py_None);
retval = E_FAIL;
Py_DECREF(result);
goto error;
}
Py_DECREF(result);
return retval;

error:
PyErr_FormatUnraisable("Exception ignored on calling "
"ctypes.DllGetClassObject");
return E_FAIL;
}

STDAPI DllGetClassObject(REFCLSID rclsid,
Expand Down Expand Up @@ -570,34 +570,33 @@ long Call_CanUnloadNow(void)

mod = PyImport_ImportModule("ctypes");
if (!mod) {
/* OutputDebugString("Could not import ctypes"); */
/* We assume that this error can only occur when shutting
down, so we silently ignore it */
PyErr_Clear();
return E_FAIL;
goto error;
}
/* Other errors cannot be raised, but are printed to stderr */
func = PyObject_GetAttrString(mod, "DllCanUnloadNow");
Py_DECREF(mod);
if (!func) {
PyErr_WriteUnraisable(context ? context : Py_None);
return E_FAIL;
goto error;
}

result = _PyObject_CallNoArgs(func);
Py_DECREF(func);
if (!result) {
PyErr_WriteUnraisable(context ? context : Py_None);
return E_FAIL;
goto error;
}

retval = PyLong_AsLong(result);
if (PyErr_Occurred()) {
PyErr_WriteUnraisable(context ? context : Py_None);
retval = E_FAIL;
Py_DECREF(result);
goto error;
}
Py_DECREF(result);
return retval;

error:
PyErr_FormatUnraisable("Exception ignored on calling "
"ctypes.DllCanUnloadNow");
return E_FAIL;
}

/*
Expand Down
2 changes: 1 addition & 1 deletion Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ clear_current_module(PyInterpreterState *interp, PyObject *expected)
goto finally;

error:
PyErr_WriteUnraisable(NULL);
PyErr_FormatUnraisable("Exception ignored on clearing _datetime module");

finally:
PyErr_SetRaisedException(exc);
Expand Down
6 changes: 4 additions & 2 deletions Modules/_io/fileio.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ fileio_dealloc_warn(PyObject *op, PyObject *source)
PyObject *exc = PyErr_GetRaisedException();
if (PyErr_ResourceWarning(source, 1, "unclosed file %R", source)) {
/* Spurious errors can appear at shutdown */
if (PyErr_ExceptionMatches(PyExc_Warning))
PyErr_WriteUnraisable((PyObject *) self);
if (PyErr_ExceptionMatches(PyExc_Warning)) {
PyErr_FormatUnraisable("Exception ignored on closing file %R",
self);
}
}
PyErr_SetRaisedException(exc);
}
Expand Down
2 changes: 1 addition & 1 deletion Modules/_io/iobase.c
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ iobase_finalize(PyObject *self)
PyErr_Clear();
res = PyObject_CallMethodNoArgs((PyObject *)self, &_Py_ID(close));
if (res == NULL) {
PyErr_WriteUnraisable(self);
PyErr_FormatUnraisable("Exception ignored on closing %R", self);
}
else {
Py_DECREF(res);
Expand Down
Loading

0 comments on commit 8e8167d

Please sign in to comment.