gh-102406: replace exception chaining by PEP-678 notes in codecs (#102407)

This commit is contained in:
Irit Katriel 2023-03-21 21:36:31 +00:00 committed by GitHub
parent e6ecd3e6b4
commit 76350e85eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 206 deletions

View file

@ -3764,113 +3764,3 @@ _PyException_AddNote(PyObject *exc, PyObject *note)
return res;
}
/* Helper to do the equivalent of "raise X from Y" in C, but always using
* the current exception rather than passing one in.
*
* We currently limit this to *only* exceptions that use the BaseException
* tp_init and tp_new methods, since we can be reasonably sure we can wrap
* those correctly without losing data and without losing backwards
* compatibility.
*
* We also aim to rule out *all* exceptions that might be storing additional
* state, whether by having a size difference relative to BaseException,
* additional arguments passed in during construction or by having a
* non-empty instance dict.
*
* We need to be very careful with what we wrap, since changing types to
* a broader exception type would be backwards incompatible for
* existing codecs, and with different init or new method implementations
* may either not support instantiation with PyErr_Format or lose
* information when instantiated that way.
*
* XXX (ncoghlan): This could be made more comprehensive by exploiting the
* fact that exceptions are expected to support pickling. If more builtin
* exceptions (e.g. AttributeError) start to be converted to rich
* exceptions with additional attributes, that's probably a better approach
* to pursue over adding special cases for particular stateful subclasses.
*
* Returns a borrowed reference to the new exception (if any), NULL if the
* existing exception was left in place.
*/
PyObject *
_PyErr_TrySetFromCause(const char *format, ...)
{
PyObject* msg_prefix;
PyObject *instance_args;
Py_ssize_t num_args, caught_type_size, base_exc_size;
va_list vargs;
int same_basic_size;
PyObject *exc = PyErr_GetRaisedException();
PyTypeObject *caught_type = Py_TYPE(exc);
/* Ensure type info indicates no extra state is stored at the C level
* and that the type can be reinstantiated using PyErr_Format
*/
caught_type_size = caught_type->tp_basicsize;
base_exc_size = _PyExc_BaseException.tp_basicsize;
same_basic_size = (
caught_type_size == base_exc_size ||
(_PyType_SUPPORTS_WEAKREFS(caught_type) &&
(caught_type_size == base_exc_size + (Py_ssize_t)sizeof(PyObject *))
)
);
if (caught_type->tp_init != (initproc)BaseException_init ||
caught_type->tp_new != BaseException_new ||
!same_basic_size ||
caught_type->tp_itemsize != _PyExc_BaseException.tp_itemsize) {
/* We can't be sure we can wrap this safely, since it may contain
* more state than just the exception type. Accordingly, we just
* leave it alone.
*/
PyErr_SetRaisedException(exc);
return NULL;
}
/* Check the args are empty or contain a single string */
instance_args = ((PyBaseExceptionObject *)exc)->args;
num_args = PyTuple_GET_SIZE(instance_args);
if (num_args > 1 ||
(num_args == 1 &&
!PyUnicode_CheckExact(PyTuple_GET_ITEM(instance_args, 0)))) {
/* More than 1 arg, or the one arg we do have isn't a string
*/
PyErr_SetRaisedException(exc);
return NULL;
}
/* Ensure the instance dict is also empty */
if (!_PyObject_IsInstanceDictEmpty(exc)) {
/* While we could potentially copy a non-empty instance dictionary
* to the replacement exception, for now we take the more
* conservative path of leaving exceptions with attributes set
* alone.
*/
PyErr_SetRaisedException(exc);
return NULL;
}
/* For exceptions that we can wrap safely, we chain the original
* exception to a new one of the exact same type with an
* error message that mentions the additional details and the
* original exception.
*
* It would be nice to wrap OSError and various other exception
* types as well, but that's quite a bit trickier due to the extra
* state potentially stored on OSError instances.
*/
va_start(vargs, format);
msg_prefix = PyUnicode_FromFormatV(format, vargs);
va_end(vargs);
if (msg_prefix == NULL) {
Py_DECREF(exc);
return NULL;
}
PyErr_Format((PyObject*)Py_TYPE(exc), "%U (%s: %S)",
msg_prefix, Py_TYPE(exc)->tp_name, exc);
Py_DECREF(msg_prefix);
PyObject *new_exc = PyErr_GetRaisedException();
PyException_SetCause(new_exc, exc);
PyErr_SetRaisedException(new_exc);
return new_exc;
}