GH-101578: Normalize the current exception (GH-101607)

* Make sure that the current exception is always normalized.

* Remove redundant type and traceback fields for the current exception.

* Add new API functions: PyErr_GetRaisedException, PyErr_SetRaisedException

* Add new API functions: PyException_GetArgs, PyException_SetArgs
This commit is contained in:
Mark Shannon 2023-02-08 09:31:12 +00:00 committed by GitHub
parent 027adf42cd
commit feec49c407
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 477 additions and 172 deletions

View file

@ -804,9 +804,7 @@ dummy_func(
DECREF_INPUTS();
}
else {
PyObject *exc_type = Py_NewRef(Py_TYPE(exc_value));
PyObject *exc_traceback = PyException_GetTraceback(exc_value);
_PyErr_Restore(tstate, exc_type, Py_NewRef(exc_value), exc_traceback);
_PyErr_SetRaisedException(tstate, Py_NewRef(exc_value));
goto exception_unwind;
}
}

View file

@ -2902,13 +2902,13 @@ format_kwargs_error(PyThreadState *tstate, PyObject *func, PyObject *kwargs)
}
}
else if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
PyObject *exc, *val, *tb;
_PyErr_Fetch(tstate, &exc, &val, &tb);
if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) {
PyObject *exc = _PyErr_GetRaisedException(tstate);
PyObject *args = ((PyBaseExceptionObject *)exc)->args;
if (exc && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1) {
_PyErr_Clear(tstate);
PyObject *funcstr = _PyObject_FunctionStr(func);
if (funcstr != NULL) {
PyObject *key = PyTuple_GET_ITEM(val, 0);
PyObject *key = PyTuple_GET_ITEM(args, 0);
_PyErr_Format(
tstate, PyExc_TypeError,
"%U got multiple values for keyword argument '%S'",
@ -2916,11 +2916,9 @@ format_kwargs_error(PyThreadState *tstate, PyObject *func, PyObject *kwargs)
Py_DECREF(funcstr);
}
Py_XDECREF(exc);
Py_XDECREF(val);
Py_XDECREF(tb);
}
else {
_PyErr_Restore(tstate, exc, val, tb);
_PyErr_SetRaisedException(tstate, exc);
}
}
}

View file

@ -27,54 +27,12 @@ static PyObject *
_PyErr_FormatV(PyThreadState *tstate, PyObject *exception,
const char *format, va_list vargs);
void
_PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value,
PyObject *traceback)
_PyErr_SetRaisedException(PyThreadState *tstate, PyObject *exc)
{
PyObject *oldtype, *oldvalue, *oldtraceback;
if (traceback != NULL && !PyTraceBack_Check(traceback)) {
/* XXX Should never happen -- fatal error instead? */
/* Well, it could be None. */
Py_SETREF(traceback, NULL);
}
/* Save these in locals to safeguard against recursive
invocation through Py_XDECREF */
oldtype = tstate->curexc_type;
oldvalue = tstate->curexc_value;
oldtraceback = tstate->curexc_traceback;
tstate->curexc_type = type;
tstate->curexc_value = value;
tstate->curexc_traceback = traceback;
Py_XDECREF(oldtype);
Py_XDECREF(oldvalue);
Py_XDECREF(oldtraceback);
}
void
PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
{
PyThreadState *tstate = _PyThreadState_GET();
_PyErr_Restore(tstate, type, value, traceback);
}
_PyErr_StackItem *
_PyErr_GetTopmostException(PyThreadState *tstate)
{
_PyErr_StackItem *exc_info = tstate->exc_info;
assert(exc_info);
while ((exc_info->exc_value == NULL || exc_info->exc_value == Py_None) &&
exc_info->previous_item != NULL)
{
exc_info = exc_info->previous_item;
}
return exc_info;
PyObject *old_exc = tstate->current_exception;
tstate->current_exception = exc;
Py_XDECREF(old_exc);
}
static PyObject*
@ -103,6 +61,80 @@ _PyErr_CreateException(PyObject *exception_type, PyObject *value)
return exc;
}
void
_PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value,
PyObject *traceback)
{
if (type == NULL) {
assert(value == NULL);
assert(traceback == NULL);
_PyErr_SetRaisedException(tstate, NULL);
return;
}
assert(PyExceptionClass_Check(type));
if (value != NULL && type == (PyObject *)Py_TYPE(value)) {
/* Already normalized */
assert(((PyBaseExceptionObject *)value)->traceback != Py_None);
}
else {
PyObject *exc = _PyErr_CreateException(type, value);
Py_XDECREF(value);
if (exc == NULL) {
Py_DECREF(type);
Py_XDECREF(traceback);
return;
}
value = exc;
}
assert(PyExceptionInstance_Check(value));
if (traceback != NULL && !PyTraceBack_Check(traceback)) {
if (traceback == Py_None) {
Py_DECREF(Py_None);
traceback = NULL;
}
else {
PyErr_SetString(PyExc_TypeError, "traceback must be a Traceback or None");
Py_XDECREF(value);
Py_DECREF(type);
Py_XDECREF(traceback);
return;
}
}
PyObject *old_traceback = ((PyBaseExceptionObject *)value)->traceback;
((PyBaseExceptionObject *)value)->traceback = traceback;
Py_XDECREF(old_traceback);
_PyErr_SetRaisedException(tstate, value);
Py_DECREF(type);
}
void
PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
{
PyThreadState *tstate = _PyThreadState_GET();
_PyErr_Restore(tstate, type, value, traceback);
}
void
PyErr_SetRaisedException(PyObject *exc)
{
PyThreadState *tstate = _PyThreadState_GET();
_PyErr_SetRaisedException(tstate, exc);
}
_PyErr_StackItem *
_PyErr_GetTopmostException(PyThreadState *tstate)
{
_PyErr_StackItem *exc_info = tstate->exc_info;
assert(exc_info);
while ((exc_info->exc_value == NULL || exc_info->exc_value == Py_None) &&
exc_info->previous_item != NULL)
{
exc_info = exc_info->previous_item;
}
return exc_info;
}
void
_PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value)
{
@ -117,30 +149,29 @@ _PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value)
exception);
return;
}
Py_XINCREF(value);
/* Normalize the exception */
if (value == NULL || (PyObject *)Py_TYPE(value) != exception) {
/* We must normalize the value right now */
PyObject *fixed_value;
/* Issue #23571: functions must not be called with an
exception set */
_PyErr_Clear(tstate);
fixed_value = _PyErr_CreateException(exception, value);
Py_XDECREF(value);
if (fixed_value == NULL) {
return;
}
value = fixed_value;
}
exc_value = _PyErr_GetTopmostException(tstate)->exc_value;
if (exc_value != NULL && exc_value != Py_None) {
/* Implicit exception chaining */
Py_INCREF(exc_value);
if (value == NULL || !PyExceptionInstance_Check(value)) {
/* We must normalize the value right now */
PyObject *fixed_value;
/* Issue #23571: functions must not be called with an
exception set */
_PyErr_Clear(tstate);
fixed_value = _PyErr_CreateException(exception, value);
Py_XDECREF(value);
if (fixed_value == NULL) {
Py_DECREF(exc_value);
return;
}
value = fixed_value;
}
/* Avoid creating new reference cycles through the
context chain, while taking care not to hang on
pre-existing ones.
@ -414,17 +445,34 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
}
PyObject *
_PyErr_GetRaisedException(PyThreadState *tstate) {
PyObject *exc = tstate->current_exception;
tstate->current_exception = NULL;
return exc;
}
PyObject *
PyErr_GetRaisedException(void)
{
PyThreadState *tstate = _PyThreadState_GET();
return _PyErr_GetRaisedException(tstate);
}
void
_PyErr_Fetch(PyThreadState *tstate, PyObject **p_type, PyObject **p_value,
PyObject **p_traceback)
{
*p_type = tstate->curexc_type;
*p_value = tstate->curexc_value;
*p_traceback = tstate->curexc_traceback;
tstate->curexc_type = NULL;
tstate->curexc_value = NULL;
tstate->curexc_traceback = NULL;
PyObject *exc = _PyErr_GetRaisedException(tstate);
*p_value = exc;
if (exc == NULL) {
*p_type = NULL;
*p_traceback = NULL;
}
else {
*p_type = Py_NewRef(Py_TYPE(exc));
*p_traceback = Py_XNewRef(((PyBaseExceptionObject *)exc)->traceback);
}
}
@ -597,6 +645,28 @@ _PyErr_ChainExceptions(PyObject *typ, PyObject *val, PyObject *tb)
}
}
/* Like PyErr_SetRaisedException(), but if an exception is already set,
set the context associated with it.
The caller is responsible for ensuring that this call won't create
any cycles in the exception context chain. */
void
_PyErr_ChainExceptions1(PyObject *exc)
{
if (exc == NULL) {
return;
}
PyThreadState *tstate = _PyThreadState_GET();
if (_PyErr_Occurred(tstate)) {
PyObject *exc2 = _PyErr_GetRaisedException(tstate);
PyException_SetContext(exc2, exc);
_PyErr_SetRaisedException(tstate, exc2);
}
else {
_PyErr_SetRaisedException(tstate, exc);
}
}
/* Set the currently set exception's context to the given exception.
If the provided exc_info is NULL, then the current Python thread state's
@ -706,19 +776,6 @@ PyErr_BadArgument(void)
return 0;
}
PyObject *
_PyErr_NoMemory(PyThreadState *tstate)
{
if (Py_IS_TYPE(PyExc_MemoryError, NULL)) {
/* PyErr_NoMemory() has been called before PyExc_MemoryError has been
initialized by _PyExc_Init() */
Py_FatalError("Out of memory and PyExc_MemoryError is not "
"initialized yet");
}
_PyErr_SetNone(tstate, PyExc_MemoryError);
return NULL;
}
PyObject *
PyErr_NoMemory(void)
{

View file

@ -1036,9 +1036,7 @@
Py_DECREF(exc_value);
}
else {
PyObject *exc_type = Py_NewRef(Py_TYPE(exc_value));
PyObject *exc_traceback = PyException_GetTraceback(exc_value);
_PyErr_Restore(tstate, exc_type, Py_NewRef(exc_value), exc_traceback);
_PyErr_SetRaisedException(tstate, Py_NewRef(exc_value));
goto exception_unwind;
}
STACK_SHRINK(2);

View file

@ -1592,6 +1592,13 @@ remove_importlib_frames(PyThreadState *tstate)
Py_DECREF(code);
tb = next;
}
assert(PyExceptionInstance_Check(value));
assert((PyObject *)Py_TYPE(value) == exception);
if (base_tb == NULL) {
base_tb = Py_None;
Py_INCREF(Py_None);
}
PyException_SetTraceback(value, base_tb);
done:
_PyErr_Restore(tstate, exception, value, base_tb);
}

View file

@ -3143,8 +3143,7 @@ init_dump_ascii_wstr(const wchar_t *str)
void
_Py_DumpPathConfig(PyThreadState *tstate)
{
PyObject *exc_type, *exc_value, *exc_tb;
_PyErr_Fetch(tstate, &exc_type, &exc_value, &exc_tb);
PyObject *exc = _PyErr_GetRaisedException(tstate);
PySys_WriteStderr("Python path configuration:\n");
@ -3202,5 +3201,5 @@ _Py_DumpPathConfig(PyThreadState *tstate)
PySys_WriteStderr(" ]\n");
}
_PyErr_Restore(tstate, exc_type, exc_value, exc_tb);
_PyErr_SetRaisedException(tstate, exc);
}

View file

@ -1375,9 +1375,7 @@ PyThreadState_Clear(PyThreadState *tstate)
Py_CLEAR(tstate->dict);
Py_CLEAR(tstate->async_exc);
Py_CLEAR(tstate->curexc_type);
Py_CLEAR(tstate->curexc_value);
Py_CLEAR(tstate->curexc_traceback);
Py_CLEAR(tstate->current_exception);
Py_CLEAR(tstate->exc_state.exc_value);

View file

@ -748,13 +748,10 @@ _Py_HandleSystemExit(int *exitcode_p)
}
done:
/* Restore and clear the exception info, in order to properly decref
* the exception, value, and traceback. If we just exit instead,
* these leak, which confuses PYTHONDUMPREFS output, and may prevent
* some finalizers from running.
*/
PyErr_Restore(exception, value, tb);
PyErr_Clear();
/* Cleanup the exception */
Py_CLEAR(exception);
Py_CLEAR(value);
Py_CLEAR(tb);
*exitcode_p = exitcode;
return 1;
}

View file

@ -66,12 +66,11 @@ _PySys_GetAttr(PyThreadState *tstate, PyObject *name)
if (sd == NULL) {
return NULL;
}
PyObject *exc_type, *exc_value, *exc_tb;
_PyErr_Fetch(tstate, &exc_type, &exc_value, &exc_tb);
PyObject *exc = _PyErr_GetRaisedException(tstate);
/* XXX Suppress a new exception if it was raised and restore
* the old one. */
PyObject *value = _PyDict_GetItemWithError(sd, name);
_PyErr_Restore(tstate, exc_type, exc_value, exc_tb);
_PyErr_SetRaisedException(tstate, exc);
return value;
}
@ -3704,11 +3703,10 @@ static void
sys_format(PyObject *key, FILE *fp, const char *format, va_list va)
{
PyObject *file, *message;
PyObject *error_type, *error_value, *error_traceback;
const char *utf8;
PyThreadState *tstate = _PyThreadState_GET();
_PyErr_Fetch(tstate, &error_type, &error_value, &error_traceback);
PyObject *error = _PyErr_GetRaisedException(tstate);
file = _PySys_GetAttr(tstate, key);
message = PyUnicode_FromFormatV(format, va);
if (message != NULL) {
@ -3720,7 +3718,7 @@ sys_format(PyObject *key, FILE *fp, const char *format, va_list va)
}
Py_DECREF(message);
}
_PyErr_Restore(tstate, error_type, error_value, error_traceback);
_PyErr_SetRaisedException(tstate, error);
}
void

View file

@ -249,6 +249,8 @@ PyTraceBack_Here(PyFrameObject *frame)
_PyErr_ChainExceptions(exc, val, tb);
return -1;
}
assert(PyExceptionInstance_Check(val));
PyException_SetTraceback(val, newtb);
PyErr_Restore(exc, val, newtb);
Py_XDECREF(tb);
return 0;
@ -260,13 +262,12 @@ void _PyTraceback_Add(const char *funcname, const char *filename, int lineno)
PyObject *globals;
PyCodeObject *code;
PyFrameObject *frame;
PyObject *exc, *val, *tb;
PyThreadState *tstate = _PyThreadState_GET();
/* Save and clear the current exception. Python functions must not be
called with an exception set. Calling Python functions happens when
the codec of the filesystem encoding is implemented in pure Python. */
_PyErr_Fetch(tstate, &exc, &val, &tb);
PyObject *exc = _PyErr_GetRaisedException(tstate);
globals = PyDict_New();
if (!globals)
@ -283,13 +284,13 @@ void _PyTraceback_Add(const char *funcname, const char *filename, int lineno)
goto error;
frame->f_lineno = lineno;
_PyErr_Restore(tstate, exc, val, tb);
_PyErr_SetRaisedException(tstate, exc);
PyTraceBack_Here(frame);
Py_DECREF(frame);
return;
error:
_PyErr_ChainExceptions(exc, val, tb);
_PyErr_ChainExceptions1(exc);
}
static PyObject *