From 87889bcbbd3eab8daaad6480f4943bedd8af8743 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 Feb 2023 14:11:06 +0000 Subject: [PATCH 01/13] Make sure that the current exception is always normalized. --- Include/internal/pycore_pyerrors.h | 6 ++ Include/pyerrors.h | 6 ++ Lib/test/test_exceptions.py | 8 +-- Modules/_testcapi/heaptype.c | 22 +++--- Objects/exceptions.c | 75 +++++++++++++++++---- Python/ceval.c | 12 ++-- Python/errors.c | 104 ++++++++++++++++++++--------- Python/import.c | 7 ++ Python/pythonrun.c | 11 ++- Python/traceback.c | 2 + 10 files changed, 182 insertions(+), 71 deletions(-) diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h index 66f37942ef916a..2e601fdfcb74a1 100644 --- a/Include/internal/pycore_pyerrors.h +++ b/Include/internal/pycore_pyerrors.h @@ -37,10 +37,16 @@ PyAPI_FUNC(void) _PyErr_Fetch( PyObject **value, PyObject **traceback); +extern PyObject * +_PyErr_Fetch1(PyThreadState *tstate); + PyAPI_FUNC(int) _PyErr_ExceptionMatches( PyThreadState *tstate, PyObject *exc); +void +_PyErr_Restore1(PyThreadState *tstate, PyObject *exc); + PyAPI_FUNC(void) _PyErr_Restore( PyThreadState *tstate, PyObject *type, diff --git a/Include/pyerrors.h b/Include/pyerrors.h index d5ac6af5b32c6c..026bb7d5ffef18 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -18,6 +18,8 @@ PyAPI_FUNC(PyObject *) PyErr_Occurred(void); PyAPI_FUNC(void) PyErr_Clear(void); PyAPI_FUNC(void) PyErr_Fetch(PyObject **, PyObject **, PyObject **); PyAPI_FUNC(void) PyErr_Restore(PyObject *, PyObject *, PyObject *); +PyAPI_FUNC(PyObject *) PyErr_Fetch1(void); +PyAPI_FUNC(void) PyErr_Restore1(PyObject *); #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030b0000 PyAPI_FUNC(PyObject*) PyErr_GetHandledException(void); PyAPI_FUNC(void) PyErr_SetHandledException(PyObject *); @@ -51,6 +53,10 @@ PyAPI_FUNC(void) PyException_SetCause(PyObject *, PyObject *); PyAPI_FUNC(PyObject *) PyException_GetContext(PyObject *); PyAPI_FUNC(void) PyException_SetContext(PyObject *, PyObject *); + +PyAPI_FUNC(PyObject *) PyException_GetArgs(PyObject *); +PyAPI_FUNC(void) PyException_SetArgs(PyObject *, PyObject *); + /* */ #define PyExceptionClass_Check(x) \ diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index f629321458d8ae..4ae71e431c56dc 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -347,6 +347,7 @@ def test_capi2(): _testcapi.raise_exception(BadException, 0) except RuntimeError as err: exc, err, tb = sys.exc_info() + tb = tb.tb_next co = tb.tb_frame.f_code self.assertEqual(co.co_name, "__init__") self.assertTrue(co.co_filename.endswith('test_exceptions.py')) @@ -1415,8 +1416,8 @@ def gen(): @cpython_only def test_recursion_normalizing_infinite_exception(self): # Issue #30697. Test that a RecursionError is raised when - # PyErr_NormalizeException() maximum recursion depth has been - # exceeded. + # maximum recursion depth has been exceeded when creating + # an exception code = """if 1: import _testcapi try: @@ -1426,8 +1427,7 @@ def test_recursion_normalizing_infinite_exception(self): """ rc, out, err = script_helper.assert_python_failure("-c", code) self.assertEqual(rc, 1) - self.assertIn(b'RecursionError: maximum recursion depth exceeded ' - b'while normalizing an exception', err) + self.assertIn(b'RecursionError: maximum recursion depth exceeded', err) self.assertIn(b'Done.', out) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index bf80fd64d80b35..ba836088256e1d 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -116,10 +116,9 @@ test_from_spec_invalid_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED( PyObject *bases = NULL; PyObject *new = NULL; PyObject *meta_error_string = NULL; - PyObject *exc_type = NULL; - PyObject *exc_value = NULL; - PyObject *exc_traceback = NULL; + PyObject *exc = NULL; PyObject *result = NULL; + PyObject *message = NULL; metaclass_a = PyType_FromSpecWithBases(&MinimalMetaclass_spec, (PyObject*)&PyType_Type); if (metaclass_a == NULL) { @@ -156,13 +155,19 @@ test_from_spec_invalid_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED( // Assert that the correct exception was raised if (PyErr_ExceptionMatches(PyExc_TypeError)) { - PyErr_Fetch(&exc_type, &exc_value, &exc_traceback); - + exc = PyErr_Fetch1(); + PyObject *args = PyException_GetArgs(exc); + if (!PyTuple_Check(args) || PyTuple_Size(args) != 1) { + PyErr_SetString(PyExc_AssertionError, + "TypeError args are not a one-tuple"); + goto finally; + } + PyObject *message = Py_NewRef(PyTuple_GET_ITEM(args, 0)); meta_error_string = PyUnicode_FromString("metaclass conflict:"); if (meta_error_string == NULL) { goto finally; } - int res = PyUnicode_Contains(exc_value, meta_error_string); + int res = PyUnicode_Contains(message, meta_error_string); if (res < 0) { goto finally; } @@ -179,9 +184,8 @@ test_from_spec_invalid_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED( Py_XDECREF(bases); Py_XDECREF(new); Py_XDECREF(meta_error_string); - Py_XDECREF(exc_type); - Py_XDECREF(exc_value); - Py_XDECREF(exc_traceback); + Py_XDECREF(exc); + Py_XDECREF(message); Py_XDECREF(class_a); Py_XDECREF(class_b); return result; diff --git a/Objects/exceptions.c b/Objects/exceptions.c index db6f7d52804d6a..13b173f8f94026 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -8,6 +8,7 @@ #include #include #include "pycore_ceval.h" // _Py_EnterRecursiveCall +#include "pycore_pyerrors.h" // struct _PyErr_Restore1 #include "pycore_exceptions.h" // struct _Py_exc_state #include "pycore_initconfig.h" #include "pycore_object.h" @@ -288,13 +289,17 @@ BaseException_set_tb(PyBaseExceptionObject *self, PyObject *tb, void *Py_UNUSED( PyErr_SetString(PyExc_TypeError, "__traceback__ may not be deleted"); return -1; } - else if (!(tb == Py_None || PyTraceBack_Check(tb))) { + if (PyTraceBack_Check(tb)) { + Py_XSETREF(self->traceback, Py_NewRef(tb)); + } + else if (tb == Py_None) { + Py_CLEAR(self->traceback); + } + else { PyErr_SetString(PyExc_TypeError, "__traceback__ must be a traceback or None"); return -1; } - - Py_XSETREF(self->traceback, Py_NewRef(tb)); return 0; } @@ -413,6 +418,20 @@ PyException_SetContext(PyObject *self, PyObject *context) Py_XSETREF(_PyBaseExceptionObject_cast(self)->context, context); } +PyObject * +PyException_GetArgs(PyObject *self) +{ + PyObject *args = _PyBaseExceptionObject_cast(self)->args; + return Py_NewRef(args); +} + +void +PyException_SetArgs(PyObject *self, PyObject *args) +{ + Py_INCREF(args); + Py_XSETREF(_PyBaseExceptionObject_cast(self)->args, args); +} + const char * PyExceptionClass_Name(PyObject *ob) { @@ -3188,20 +3207,19 @@ SimpleExtendsException(PyExc_Exception, ReferenceError, #define MEMERRORS_SAVE 16 +static PyBaseExceptionObject last_resort_memory_error; + static PyObject * -MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +get_memory_error(int allow_allocation, PyObject *args, PyObject *kwds) { PyBaseExceptionObject *self; - - /* If this is a subclass of MemoryError, don't use the freelist - * and just return a fresh object */ - if (type != (PyTypeObject *) PyExc_MemoryError) { - return BaseException_new(type, args, kwds); - } - struct _Py_exc_state *state = get_exc_state(); if (state->memerrors_freelist == NULL) { - return BaseException_new(type, args, kwds); + if (!allow_allocation) { + return Py_NewRef(&last_resort_memory_error); + } + PyObject *result = BaseException_new((PyTypeObject *)PyExc_MemoryError, args, kwds); + return result; } /* Fetch object from freelist and revive it */ @@ -3221,6 +3239,35 @@ MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return (PyObject *)self; } +static PyBaseExceptionObject last_resort_memory_error; + +static PyObject * +MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + /* If this is a subclass of MemoryError, don't use the freelist + * and just return a fresh object */ + if (type != (PyTypeObject *) PyExc_MemoryError) { + return BaseException_new(type, args, kwds); + } + return get_memory_error(1, args, kwds); +} + +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"); + } + PyObject *err = get_memory_error(0, NULL, NULL); + if (err != NULL) { + _PyErr_Restore1(tstate, err); + } + return NULL; +} + static void MemoryError_dealloc(PyBaseExceptionObject *self) { @@ -3252,6 +3299,7 @@ preallocate_memerrors(void) /* We create enough MemoryErrors and then decref them, which will fill up the freelist. */ int i; + PyObject *errors[MEMERRORS_SAVE]; for (i = 0; i < MEMERRORS_SAVE; i++) { errors[i] = MemoryError_new((PyTypeObject *) PyExc_MemoryError, @@ -3291,6 +3339,9 @@ static PyTypeObject _PyExc_MemoryError = { }; PyObject *PyExc_MemoryError = (PyObject *) &_PyExc_MemoryError; +static PyBaseExceptionObject last_resort_memory_error = { + _PyObject_IMMORTAL_INIT(&_PyExc_MemoryError) +}; /* * BufferError extends Exception diff --git a/Python/ceval.c b/Python/ceval.c index 2e6fed580dede4..e882fff824480a 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2905,13 +2905,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_Fetch1(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'", @@ -2919,11 +2919,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_Restore1(tstate, exc); } } } diff --git a/Python/errors.c b/Python/errors.c index 05ef62246ec0a4..8a846c0825e389 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -27,6 +27,43 @@ static PyObject * _PyErr_FormatV(PyThreadState *tstate, PyObject *exception, const char *format, va_list vargs); +void +ASSERT_EXCEPTIONS_NORMALIZED(PyThreadState *tstate) +{ + /* Exceptions are normalized if all NULL, + * or if curexc_type = Py_TYPE(curexc_value) and + * curexc_traceback = curexc_value->traceback + * and both type and traceback are valid */ + if (tstate->curexc_value == NULL) { + assert(tstate->curexc_type == NULL); + assert(tstate->curexc_traceback == NULL); + } + else { + assert(((PyBaseExceptionObject *)tstate->curexc_value)->traceback != Py_None); + assert(tstate->curexc_traceback != Py_None); + assert(PyExceptionClass_Check(tstate->curexc_type)); + assert(tstate->curexc_type == (PyObject *)Py_TYPE(tstate->curexc_value)); + assert( + tstate->curexc_traceback == + ((PyBaseExceptionObject *)tstate->curexc_value)->traceback + ); + } +} + +void +_PyErr_Restore1(PyThreadState *tstate, PyObject *exc) +{ + if (exc == NULL) { + _PyErr_Restore(tstate, NULL, NULL, NULL); + } + else { + _PyErr_Restore(tstate, + Py_NewRef(Py_TYPE(exc)), + exc, + Py_XNewRef(((PyBaseExceptionObject *)exc)->traceback) + ); + } +} void _PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value, @@ -53,6 +90,7 @@ _PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value, Py_XDECREF(oldtype); Py_XDECREF(oldvalue); Py_XDECREF(oldtraceback); + ASSERT_EXCEPTIONS_NORMALIZED(tstate); } void @@ -117,30 +155,29 @@ _PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value) exception); return; } - Py_XINCREF(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; + /* 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) { - Py_DECREF(exc_value); - return; - } + /* Issue #23571: functions must not be called with an + exception set */ + _PyErr_Clear(tstate); - value = fixed_value; + 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); /* Avoid creating new reference cycles through the context chain, while taking care not to hang on pre-existing ones. @@ -414,6 +451,22 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb) } +PyObject * +_PyErr_Fetch1(PyThreadState *tstate) { + PyObject *exc = tstate->curexc_value; + tstate->curexc_value = NULL; + Py_CLEAR(tstate->curexc_type); + Py_CLEAR(tstate->curexc_traceback); + return exc; +} + +PyObject * +PyErr_Fetch1(void) +{ + PyThreadState *tstate = _PyThreadState_GET(); + return _PyErr_Fetch1(tstate); +} + void _PyErr_Fetch(PyThreadState *tstate, PyObject **p_type, PyObject **p_value, PyObject **p_traceback) @@ -706,19 +759,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) { diff --git a/Python/import.c b/Python/import.c index da6c15c5fd4144..1318c09d9b3212 100644 --- a/Python/import.c +++ b/Python/import.c @@ -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); } diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 35292b6478a833..6a4d593768690a 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -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; } diff --git a/Python/traceback.c b/Python/traceback.c index da26c9b260a3bd..8935a3179a479d 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -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; From 14f21ff5f85f22def0b3a166457b8718cededd0b Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 Feb 2023 14:39:15 +0000 Subject: [PATCH 02/13] Remove redundant type and traceback fields for the current exception. --- Include/cpython/pystate.h | 4 +- Include/internal/pycore_pyerrors.h | 5 +- Objects/object.c | 12 ++--- Parser/pegen.c | 15 +++--- Python/errors.c | 84 +++++++++++------------------- Python/pystate.c | 4 +- 6 files changed, 48 insertions(+), 76 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index f5db52f76e8f4f..be1fcb61fa2244 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -166,9 +166,7 @@ struct _ts { PyObject *c_traceobj; /* The exception currently being raised */ - PyObject *curexc_type; - PyObject *curexc_value; - PyObject *curexc_traceback; + PyObject *current_exception; /* Pointer to the top of the exception stack for the exceptions * we may be currently handling. (See _PyErr_StackItem above.) diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h index 2e601fdfcb74a1..ec4e4631ae504f 100644 --- a/Include/internal/pycore_pyerrors.h +++ b/Include/internal/pycore_pyerrors.h @@ -20,7 +20,10 @@ extern void _PyErr_FiniTypes(PyInterpreterState *); static inline PyObject* _PyErr_Occurred(PyThreadState *tstate) { assert(tstate != NULL); - return tstate->curexc_type; + if (tstate->current_exception == NULL) { + return NULL; + } + return (PyObject *)Py_TYPE(tstate->current_exception); } static inline void _PyErr_ClearExcState(_PyErr_StackItem *exc_state) diff --git a/Objects/object.c b/Objects/object.c index e940222c657e3c..7817c04ef5f5be 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2416,10 +2416,10 @@ _Py_Dealloc(PyObject *op) destructor dealloc = type->tp_dealloc; #ifdef Py_DEBUG PyThreadState *tstate = _PyThreadState_GET(); - PyObject *old_exc_type = tstate != NULL ? tstate->curexc_type : NULL; + PyObject *old_exc = tstate != NULL ? tstate->current_exception : NULL; // Keep the old exception type alive to prevent undefined behavior // on (tstate->curexc_type != old_exc_type) below - Py_XINCREF(old_exc_type); + Py_XINCREF(old_exc); // Make sure that type->tp_name remains valid Py_INCREF(type); #endif @@ -2432,12 +2432,12 @@ _Py_Dealloc(PyObject *op) #ifdef Py_DEBUG // gh-89373: The tp_dealloc function must leave the current exception // unchanged. - if (tstate != NULL && tstate->curexc_type != old_exc_type) { + if (tstate != NULL && tstate->current_exception != old_exc) { const char *err; - if (old_exc_type == NULL) { + if (old_exc == NULL) { err = "Deallocator of type '%s' raised an exception"; } - else if (tstate->curexc_type == NULL) { + else if (tstate->current_exception == NULL) { err = "Deallocator of type '%s' cleared the current exception"; } else { @@ -2448,7 +2448,7 @@ _Py_Dealloc(PyObject *op) } _Py_FatalErrorFormat(__func__, err, type->tp_name); } - Py_XDECREF(old_exc_type); + Py_XDECREF(old_exc); Py_DECREF(type); #endif } diff --git a/Parser/pegen.c b/Parser/pegen.c index d84e06861edefc..52fce132008dd3 100644 --- a/Parser/pegen.c +++ b/Parser/pegen.c @@ -643,13 +643,10 @@ _PyPegen_number_token(Parser *p) PyThreadState *tstate = _PyThreadState_GET(); // The only way a ValueError should happen in _this_ code is via // PyLong_FromString hitting a length limit. - if (tstate->curexc_type == PyExc_ValueError && - tstate->curexc_value != NULL) { - PyObject *type, *value, *tb; - // This acts as PyErr_Clear() as we're replacing curexc. - PyErr_Fetch(&type, &value, &tb); - Py_XDECREF(tb); - Py_DECREF(type); + if (tstate->current_exception != NULL && + Py_TYPE(tstate->current_exception) == (PyTypeObject *)PyExc_ValueError + ) { + PyObject *exc = PyErr_Fetch1(); /* Intentionally omitting columns to avoid a wall of 1000s of '^'s * on the error message. Nobody is going to overlook their huge * numeric literal once given the line. */ @@ -659,8 +656,8 @@ _PyPegen_number_token(Parser *p) t->end_lineno, -1 /* end_col_offset */, "%S - Consider hexadecimal for huge integer literals " "to avoid decimal conversion limits.", - value); - Py_DECREF(value); + exc); + Py_DECREF(exc); } return NULL; } diff --git a/Python/errors.c b/Python/errors.c index 8a846c0825e389..e43d7a0f06fa47 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -28,24 +28,24 @@ _PyErr_FormatV(PyThreadState *tstate, PyObject *exception, const char *format, va_list vargs); void -ASSERT_EXCEPTIONS_NORMALIZED(PyThreadState *tstate) +ASSERT_EXCEPTION_NORMALIZED(PyObject *type, PyObject *value, + PyObject *traceback) { /* Exceptions are normalized if all NULL, * or if curexc_type = Py_TYPE(curexc_value) and * curexc_traceback = curexc_value->traceback * and both type and traceback are valid */ - if (tstate->curexc_value == NULL) { - assert(tstate->curexc_type == NULL); - assert(tstate->curexc_traceback == NULL); + if (value == NULL) { + assert(type == NULL); + assert(traceback == NULL); } else { - assert(((PyBaseExceptionObject *)tstate->curexc_value)->traceback != Py_None); - assert(tstate->curexc_traceback != Py_None); - assert(PyExceptionClass_Check(tstate->curexc_type)); - assert(tstate->curexc_type == (PyObject *)Py_TYPE(tstate->curexc_value)); - assert( - tstate->curexc_traceback == - ((PyBaseExceptionObject *)tstate->curexc_value)->traceback + assert(PyExceptionClass_Check(type)); + assert(type == (PyObject *)Py_TYPE(value)); + assert(((PyBaseExceptionObject *)value)->traceback != Py_None); + assert(traceback == ((PyBaseExceptionObject *)value)->traceback || + (traceback == Py_None && + ((PyBaseExceptionObject *)value)->traceback == NULL) ); } } @@ -53,44 +53,19 @@ ASSERT_EXCEPTIONS_NORMALIZED(PyThreadState *tstate) void _PyErr_Restore1(PyThreadState *tstate, PyObject *exc) { - if (exc == NULL) { - _PyErr_Restore(tstate, NULL, NULL, NULL); - } - else { - _PyErr_Restore(tstate, - Py_NewRef(Py_TYPE(exc)), - exc, - Py_XNewRef(((PyBaseExceptionObject *)exc)->traceback) - ); - } + PyObject *old_exc = tstate->current_exception; + tstate->current_exception = exc; + Py_XDECREF(old_exc); } void _PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *traceback) { - 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); - ASSERT_EXCEPTIONS_NORMALIZED(tstate); + ASSERT_EXCEPTION_NORMALIZED(type, value, traceback); + _PyErr_Restore1(tstate, value); + Py_XDECREF(type); + Py_XDECREF(traceback); } void @@ -453,10 +428,8 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb) PyObject * _PyErr_Fetch1(PyThreadState *tstate) { - PyObject *exc = tstate->curexc_value; - tstate->curexc_value = NULL; - Py_CLEAR(tstate->curexc_type); - Py_CLEAR(tstate->curexc_traceback); + PyObject *exc = tstate->current_exception; + tstate->current_exception = NULL; return exc; } @@ -471,13 +444,16 @@ 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_Fetch1(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); + } } diff --git a/Python/pystate.c b/Python/pystate.c index 8bb49d954a81b6..401d67e303dd5c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1379,9 +1379,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); From 8b62323f4575f5147f9a8889f66764b70c52ba05 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 Feb 2023 15:01:18 +0000 Subject: [PATCH 03/13] Replace a some uses of PyErr_Fetch with PyErr_Fetch1. --- Include/cpython/pyerrors.h | 1 + Modules/gcmodule.c | 5 ++--- Objects/dictobject.c | 6 +++--- Python/bytecodes.c | 4 +--- Python/errors.c | 22 ++++++++++++++++++++++ Python/initconfig.c | 5 ++--- Python/sysmodule.c | 10 ++++------ Python/traceback.c | 7 +++---- 8 files changed, 38 insertions(+), 22 deletions(-) diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index 141341667795e8..0d9cc9922f7368 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -99,6 +99,7 @@ PyAPI_FUNC(void) _PyErr_GetExcInfo(PyThreadState *, PyObject **, PyObject **, Py /* Context manipulation (PEP 3134) */ PyAPI_FUNC(void) _PyErr_ChainExceptions(PyObject *, PyObject *, PyObject *); +PyAPI_FUNC(void) _PyErr_ChainExceptions1(PyObject *); /* Like PyErr_Format(), but saves current exception as __context__ and __cause__. diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 6630faa6f4471d..7f129ec6c78a7b 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -2082,11 +2082,10 @@ PyGC_Collect(void) n = 0; } else { - PyObject *exc, *value, *tb; gcstate->collecting = 1; - _PyErr_Fetch(tstate, &exc, &value, &tb); + PyObject *exc = _PyErr_Fetch1(tstate); n = gc_collect_with_callback(tstate, NUM_GENERATIONS - 1); - _PyErr_Restore(tstate, exc, value, tb); + _PyErr_Restore1(tstate, exc); gcstate->collecting = 0; } diff --git a/Objects/dictobject.c b/Objects/dictobject.c index b9067213820b52..7b7f4380c99cdc 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1663,15 +1663,15 @@ PyDict_GetItem(PyObject *op, PyObject *key) #endif /* Preserve the existing exception */ - PyObject *exc_type, *exc_value, *exc_tb; PyObject *value; Py_ssize_t ix; (void)ix; - _PyErr_Fetch(tstate, &exc_type, &exc_value, &exc_tb); + + PyObject *exc = _PyErr_Fetch1(tstate); ix = _Py_dict_lookup(mp, key, hash, &value); /* Ignore any exception raised by the lookup */ - _PyErr_Restore(tstate, exc_type, exc_value, exc_tb); + _PyErr_Restore1(tstate, exc); assert(ix >= 0 || value == NULL); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 8993567ac82206..4b98320eca9c79 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -787,9 +787,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_Restore1(tstate, Py_NewRef(exc_value)); goto exception_unwind; } } diff --git a/Python/errors.c b/Python/errors.c index e43d7a0f06fa47..93057ba654d7d9 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -626,6 +626,28 @@ _PyErr_ChainExceptions(PyObject *typ, PyObject *val, PyObject *tb) } } +/* Like PyErr_Restore1(), 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_Fetch1(tstate); + PyException_SetContext(exc2, exc); + _PyErr_Restore1(tstate, exc2); + } + else { + _PyErr_Restore1(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 diff --git a/Python/initconfig.c b/Python/initconfig.c index d7b2dc4a297425..9843de207553ba 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -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_Fetch1(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_Restore1(tstate, exc); } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index f9f766a94d1464..762188a13a2f28 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -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_Fetch1(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_Restore1(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_Fetch1(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_Restore1(tstate, error); } void diff --git a/Python/traceback.c b/Python/traceback.c index 8935a3179a479d..6482a8bd9d7e35 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -262,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_Fetch1(tstate); globals = PyDict_New(); if (!globals) @@ -285,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_Restore1(tstate, exc); PyTraceBack_Here(frame); Py_DECREF(frame); return; error: - _PyErr_ChainExceptions(exc, val, tb); + _PyErr_ChainExceptions1(exc); } static PyObject * From 508850f3840a123dd926f37ebe2a95cefaa7f81f Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 Feb 2023 15:12:09 +0000 Subject: [PATCH 04/13] Refactor assertion. --- Python/errors.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/Python/errors.c b/Python/errors.c index 93057ba654d7d9..8c2c635665001e 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -28,10 +28,21 @@ _PyErr_FormatV(PyThreadState *tstate, PyObject *exception, const char *format, va_list vargs); void -ASSERT_EXCEPTION_NORMALIZED(PyObject *type, PyObject *value, +_PyErr_Restore1(PyThreadState *tstate, PyObject *exc) +{ + PyObject *old_exc = tstate->current_exception; + tstate->current_exception = exc; + Py_XDECREF(old_exc); +} + +void +_PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *traceback) { - /* Exceptions are normalized if all NULL, +#ifdef Py_DEBUG + /* Check that we are being passed a normalized exception. + * + * Exceptions are normalized if all NULL, * or if curexc_type = Py_TYPE(curexc_value) and * curexc_traceback = curexc_value->traceback * and both type and traceback are valid */ @@ -48,21 +59,8 @@ ASSERT_EXCEPTION_NORMALIZED(PyObject *type, PyObject *value, ((PyBaseExceptionObject *)value)->traceback == NULL) ); } -} - -void -_PyErr_Restore1(PyThreadState *tstate, PyObject *exc) -{ - PyObject *old_exc = tstate->current_exception; - tstate->current_exception = exc; - Py_XDECREF(old_exc); -} +#endif -void -_PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value, - PyObject *traceback) -{ - ASSERT_EXCEPTION_NORMALIZED(type, value, traceback); _PyErr_Restore1(tstate, value); Py_XDECREF(type); Py_XDECREF(traceback); From 073409a946efc1fae09ee2134634f00fe98c2dd8 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 Feb 2023 15:39:51 +0000 Subject: [PATCH 05/13] Document new C-API functions --- Doc/c-api/exceptions.rst | 62 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 087e0a61d12d59..402e2c83977bba 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -402,6 +402,8 @@ Querying the error indicator .. c:function:: void PyErr_Fetch(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback) + As of 3.12, this function is deprecated. Use :c:func:`PyErr_Fetch1` instead. + Retrieve the error indicator into three variables whose addresses are passed. If the error indicator is not set, set all three variables to ``NULL``. If it is set, it will be cleared and you own a reference to each object retrieved. The @@ -422,6 +424,49 @@ Querying the error indicator } +.. c:function:: PyObject *PyErr_Fetch1(void) + + Returns the current error indicator, clearing the error indicator at the same time. + + .. note:: + + This function is normally only used by code that needs to catch exceptions or + by code that needs to save and restore the error indicator temporarily, e.g.:: + + { + PyObject *exc = PyErr_Fetch1(); + + /* ... code that might produce other errors ... */ + + PyErr_Restore1(exc); + } + + .. versionadded:: 3.12 + + +.. c:function:: void PyErr_Restore1(PyObject *exc) + + As of 3.12, this function is deprecated. Use :c:func:`PyErr_Restore1` instead. + + Set the error indicator to ``exc``. + If the error indicator is already set, it is cleared first. + + ``exc`` should be a valid exception. + (Violating this rules will cause subtle problems later.) + This call consumes a reference to the ``exc`` object: you must own a + reference to that object before the call and after the call you no longer own + that reference. + (If you don't understand this, don't use this function. I warned you.) + + .. note:: + + This function is normally only used by code that needs to save and restore the + error indicator temporarily. Use :c:func:`PyErr_Fetch` to save the current + error indicator. + + .. versionadded:: 3.12 + + .. c:function:: void PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) Set the error indicator from the three objects. If the error indicator is @@ -437,12 +482,15 @@ Querying the error indicator .. note:: This function is normally only used by code that needs to save and restore the - error indicator temporarily. Use :c:func:`PyErr_Fetch` to save the current + error indicator temporarily. Use :c:func:`PyErr_Fetch1` to save the current error indicator. .. c:function:: void PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb) + As of 3.12, this function is deprecated. + Use :c:func:`PyErr_Fetch1` instead of :c:func:`PyErr_Fetch`. + Under certain circumstances, the values returned by :c:func:`PyErr_Fetch` below can be "unnormalized", meaning that ``*exc`` is a class object but ``*val`` is not an instance of the same class. This function can be used to instantiate @@ -704,6 +752,18 @@ Exception Objects :attr:`__suppress_context__` is implicitly set to ``True`` by this function. +.. c:function:: PyObject* PyException_GetArgs(PyObject *ex) + + Return args of the given exception as a new reference, + as accessible from Python through :attr:`args`. + + +.. c:function:: void PyException_SetArgs(PyObject *ex, PyObject *args) + + Set the args of the given exception, + as accessible from Python through :attr:`args`. + + .. _unicodeexceptions: Unicode Exception Objects From d260b13f6dc74c591dcd93f87d7892959e6bc4f1 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 Feb 2023 16:14:40 +0000 Subject: [PATCH 06/13] Add news --- .../C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst diff --git a/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst b/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst new file mode 100644 index 00000000000000..58fcf8717b2892 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst @@ -0,0 +1,5 @@ +Deprecate the three arguments forms of :cfunc`PyErr_Fetch` and +:cfunc:`PyErr_Restore` and replace them with one argument forms that take +and return the exception object. Also add :cfunc`PyException_GetArgs` and +:cfunc:`PyException_SetArgs` as convenience functions to help dealing with +exceptions in the C API. From d5480e9a4cb0002a8f7e7e18a94b69df6d21ced5 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 Feb 2023 16:17:56 +0000 Subject: [PATCH 07/13] Fix typos --- .../next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst b/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst index 58fcf8717b2892..3f731fa7c3cb79 100644 --- a/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst +++ b/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst @@ -1,5 +1,5 @@ -Deprecate the three arguments forms of :cfunc`PyErr_Fetch` and +Deprecate the three arguments forms of :cfunc:`PyErr_Fetch` and :cfunc:`PyErr_Restore` and replace them with one argument forms that take -and return the exception object. Also add :cfunc`PyException_GetArgs` and +and return the exception object. Also add :cfunc:`PyException_GetArgs` and :cfunc:`PyException_SetArgs` as convenience functions to help dealing with exceptions in the C API. From 0c3dc7bf04eec6b6c7ec85eaa9e5ec899448e820 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 6 Feb 2023 18:30:06 +0000 Subject: [PATCH 08/13] Update generated code. --- Python/generated_cases.c.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index e524bfcb99d470..e98ef6ec07f260 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1019,9 +1019,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_Restore1(tstate, Py_NewRef(exc_value)); goto exception_unwind; } STACK_SHRINK(2); From aa70bad5b4f8d5698f068842c8714b068cdedcd7 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 7 Feb 2023 10:52:01 +0000 Subject: [PATCH 09/13] Rename new functions and fix up docs. --- Doc/c-api/exceptions.rst | 87 ++++++++++++++++++------------ Include/internal/pycore_pyerrors.h | 4 +- Include/pyerrors.h | 4 +- Modules/_testcapi/heaptype.c | 2 +- Modules/gcmodule.c | 4 +- Objects/dictobject.c | 4 +- Objects/exceptions.c | 4 +- Parser/pegen.c | 2 +- Python/bytecodes.c | 2 +- Python/ceval.c | 4 +- Python/errors.c | 20 +++---- Python/generated_cases.c.h | 2 +- Python/initconfig.c | 4 +- Python/sysmodule.c | 8 +-- Python/traceback.c | 4 +- 15 files changed, 86 insertions(+), 69 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 402e2c83977bba..de9b15edd6859a 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -400,14 +400,11 @@ Querying the error indicator recursively in subtuples) are searched for a match. -.. c:function:: void PyErr_Fetch(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback) +.. c:function:: PyObject *PyErr_GetRaisedException(void) - As of 3.12, this function is deprecated. Use :c:func:`PyErr_Fetch1` instead. - - Retrieve the error indicator into three variables whose addresses are passed. - If the error indicator is not set, set all three variables to ``NULL``. If it is - set, it will be cleared and you own a reference to each object retrieved. The - value and traceback object may be ``NULL`` even when the type object is not. + Returns the exception currently being raised, clearing the exception at + the same time. Do not confuse this with the exception currently being + handled which can be accessed with :c:func:`PyErr_GetHandledException`. .. note:: @@ -415,61 +412,76 @@ Querying the error indicator by code that needs to save and restore the error indicator temporarily, e.g.:: { - PyObject *type, *value, *traceback; - PyErr_Fetch(&type, &value, &traceback); + PyObject *exc = PyErr_GetRaisedException(); /* ... code that might produce other errors ... */ - PyErr_Restore(type, value, traceback); + PyErr_SetRaisedException(exc); } + .. versionadded:: 3.12 + -.. c:function:: PyObject *PyErr_Fetch1(void) +.. c:function:: void PyErr_SetRaisedException(PyObject *exc) - Returns the current error indicator, clearing the error indicator at the same time. + Sets the exception currently being raised ``exc``. + If the exception is already set, it is cleared first. + + ``exc`` must be a valid exception. + (Violating this rules will cause subtle problems later.) + This call consumes a reference to the ``exc`` object: you must own a + reference to that object before the call and after the call you no longer own + that reference. + (If you don't understand this, don't use this function. I warned you.) .. note:: - This function is normally only used by code that needs to catch exceptions or - by code that needs to save and restore the error indicator temporarily, e.g.:: + This function is normally only used by code that needs to save and restore the + error indicator temporarily. Use :c:func:`PyErr_GetRaisedException` to save + the current exception, e.g.:: { - PyObject *exc = PyErr_Fetch1(); + PyObject *exc = PyErr_GetRaisedException(); /* ... code that might produce other errors ... */ - PyErr_Restore1(exc); + PyErr_SetRaisedException(exc); } .. versionadded:: 3.12 -.. c:function:: void PyErr_Restore1(PyObject *exc) - - As of 3.12, this function is deprecated. Use :c:func:`PyErr_Restore1` instead. +.. c:function:: void PyErr_Fetch(PyObject **ptype, PyObject **pvalue, PyObject **ptraceback) - Set the error indicator to ``exc``. - If the error indicator is already set, it is cleared first. + As of 3.12, this function is deprecated. Use :c:func:`PyErr_GetRaisedException` instead. - ``exc`` should be a valid exception. - (Violating this rules will cause subtle problems later.) - This call consumes a reference to the ``exc`` object: you must own a - reference to that object before the call and after the call you no longer own - that reference. - (If you don't understand this, don't use this function. I warned you.) + Retrieve the error indicator into three variables whose addresses are passed. + If the error indicator is not set, set all three variables to ``NULL``. If it is + set, it will be cleared and you own a reference to each object retrieved. The + value and traceback object may be ``NULL`` even when the type object is not. .. note:: - This function is normally only used by code that needs to save and restore the - error indicator temporarily. Use :c:func:`PyErr_Fetch` to save the current - error indicator. + This function is normally only used by code that needs to catch exceptions or + by code that needs to save and restore the error indicator temporarily, e.g.:: - .. versionadded:: 3.12 + { + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + + /* ... code that might produce other errors ... */ + + PyErr_Restore(type, value, traceback); + } + + .. deprecated:: 3.12 .. c:function:: void PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) - Set the error indicator from the three objects. If the error indicator is + As of 3.12, this function is deprecated. Use :c:func:`PyErr_SetRaisedException` instead. + + Set the error indicator from the three objects. If the error indicator is already set, it is cleared first. If the objects are ``NULL``, the error indicator is cleared. Do not pass a ``NULL`` type and non-``NULL`` value or traceback. The exception type should be a class. Do not pass an invalid @@ -482,14 +494,17 @@ Querying the error indicator .. note:: This function is normally only used by code that needs to save and restore the - error indicator temporarily. Use :c:func:`PyErr_Fetch1` to save the current + error indicator temporarily. Use :c:func:`PyErr_Fetch` to save the current error indicator. + .. deprecated:: 3.12 + .. c:function:: void PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb) - As of 3.12, this function is deprecated. - Use :c:func:`PyErr_Fetch1` instead of :c:func:`PyErr_Fetch`. + As of 3.12, this function is deprecated. + Use :c:func:`PyErr_GetRaisedException` instead of :c:func:`PyErr_Fetch` to avoid + any possible de-normalization. Under certain circumstances, the values returned by :c:func:`PyErr_Fetch` below can be "unnormalized", meaning that ``*exc`` is a class object but ``*val`` is @@ -507,6 +522,8 @@ Querying the error indicator PyException_SetTraceback(val, tb); } + .. deprecated:: 3.12 + .. c:function:: PyObject* PyErr_GetHandledException(void) diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h index ec4e4631ae504f..1bb4a9aa103898 100644 --- a/Include/internal/pycore_pyerrors.h +++ b/Include/internal/pycore_pyerrors.h @@ -41,14 +41,14 @@ PyAPI_FUNC(void) _PyErr_Fetch( PyObject **traceback); extern PyObject * -_PyErr_Fetch1(PyThreadState *tstate); +_PyErr_GetRaisedException(PyThreadState *tstate); PyAPI_FUNC(int) _PyErr_ExceptionMatches( PyThreadState *tstate, PyObject *exc); void -_PyErr_Restore1(PyThreadState *tstate, PyObject *exc); +_PyErr_SetRaisedException(PyThreadState *tstate, PyObject *exc); PyAPI_FUNC(void) _PyErr_Restore( PyThreadState *tstate, diff --git a/Include/pyerrors.h b/Include/pyerrors.h index 026bb7d5ffef18..d089fa71779330 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -18,8 +18,8 @@ PyAPI_FUNC(PyObject *) PyErr_Occurred(void); PyAPI_FUNC(void) PyErr_Clear(void); PyAPI_FUNC(void) PyErr_Fetch(PyObject **, PyObject **, PyObject **); PyAPI_FUNC(void) PyErr_Restore(PyObject *, PyObject *, PyObject *); -PyAPI_FUNC(PyObject *) PyErr_Fetch1(void); -PyAPI_FUNC(void) PyErr_Restore1(PyObject *); +PyAPI_FUNC(PyObject *) PyErr_GetRaisedException(void); +PyAPI_FUNC(void) PyErr_SetRaisedException(PyObject *); #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030b0000 PyAPI_FUNC(PyObject*) PyErr_GetHandledException(void); PyAPI_FUNC(void) PyErr_SetHandledException(PyObject *); diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index ba836088256e1d..a6f5a70ae4c333 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -155,7 +155,7 @@ test_from_spec_invalid_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED( // Assert that the correct exception was raised if (PyErr_ExceptionMatches(PyExc_TypeError)) { - exc = PyErr_Fetch1(); + exc = PyErr_GetRaisedException(); PyObject *args = PyException_GetArgs(exc); if (!PyTuple_Check(args) || PyTuple_Size(args) != 1) { PyErr_SetString(PyExc_AssertionError, diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 7f129ec6c78a7b..5879c5e11fe14a 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -2083,9 +2083,9 @@ PyGC_Collect(void) } else { gcstate->collecting = 1; - PyObject *exc = _PyErr_Fetch1(tstate); + PyObject *exc = _PyErr_GetRaisedException(tstate); n = gc_collect_with_callback(tstate, NUM_GENERATIONS - 1); - _PyErr_Restore1(tstate, exc); + _PyErr_SetRaisedException(tstate, exc); gcstate->collecting = 0; } diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 7b7f4380c99cdc..fc658ca2f4b7f8 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1667,11 +1667,11 @@ PyDict_GetItem(PyObject *op, PyObject *key) Py_ssize_t ix; (void)ix; - PyObject *exc = _PyErr_Fetch1(tstate); + PyObject *exc = _PyErr_GetRaisedException(tstate); ix = _Py_dict_lookup(mp, key, hash, &value); /* Ignore any exception raised by the lookup */ - _PyErr_Restore1(tstate, exc); + _PyErr_SetRaisedException(tstate, exc); assert(ix >= 0 || value == NULL); diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 13b173f8f94026..976f84dbf63c93 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -8,7 +8,7 @@ #include #include #include "pycore_ceval.h" // _Py_EnterRecursiveCall -#include "pycore_pyerrors.h" // struct _PyErr_Restore1 +#include "pycore_pyerrors.h" // struct _PyErr_SetRaisedException #include "pycore_exceptions.h" // struct _Py_exc_state #include "pycore_initconfig.h" #include "pycore_object.h" @@ -3263,7 +3263,7 @@ _PyErr_NoMemory(PyThreadState *tstate) } PyObject *err = get_memory_error(0, NULL, NULL); if (err != NULL) { - _PyErr_Restore1(tstate, err); + _PyErr_SetRaisedException(tstate, err); } return NULL; } diff --git a/Parser/pegen.c b/Parser/pegen.c index 52fce132008dd3..94dd9de8a431c1 100644 --- a/Parser/pegen.c +++ b/Parser/pegen.c @@ -646,7 +646,7 @@ _PyPegen_number_token(Parser *p) if (tstate->current_exception != NULL && Py_TYPE(tstate->current_exception) == (PyTypeObject *)PyExc_ValueError ) { - PyObject *exc = PyErr_Fetch1(); + PyObject *exc = PyErr_GetRaisedException(); /* Intentionally omitting columns to avoid a wall of 1000s of '^'s * on the error message. Nobody is going to overlook their huge * numeric literal once given the line. */ diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 4b98320eca9c79..5ce1eb4b207825 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -787,7 +787,7 @@ dummy_func( DECREF_INPUTS(); } else { - _PyErr_Restore1(tstate, Py_NewRef(exc_value)); + _PyErr_SetRaisedException(tstate, Py_NewRef(exc_value)); goto exception_unwind; } } diff --git a/Python/ceval.c b/Python/ceval.c index e882fff824480a..db7deb93352e08 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2905,7 +2905,7 @@ format_kwargs_error(PyThreadState *tstate, PyObject *func, PyObject *kwargs) } } else if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { - PyObject *exc = _PyErr_Fetch1(tstate); + PyObject *exc = _PyErr_GetRaisedException(tstate); PyObject *args = ((PyBaseExceptionObject *)exc)->args; if (exc && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1) { _PyErr_Clear(tstate); @@ -2921,7 +2921,7 @@ format_kwargs_error(PyThreadState *tstate, PyObject *func, PyObject *kwargs) Py_XDECREF(exc); } else { - _PyErr_Restore1(tstate, exc); + _PyErr_SetRaisedException(tstate, exc); } } } diff --git a/Python/errors.c b/Python/errors.c index 8c2c635665001e..5ad6418c117ff4 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -28,7 +28,7 @@ _PyErr_FormatV(PyThreadState *tstate, PyObject *exception, const char *format, va_list vargs); void -_PyErr_Restore1(PyThreadState *tstate, PyObject *exc) +_PyErr_SetRaisedException(PyThreadState *tstate, PyObject *exc) { PyObject *old_exc = tstate->current_exception; tstate->current_exception = exc; @@ -61,7 +61,7 @@ _PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value, } #endif - _PyErr_Restore1(tstate, value); + _PyErr_SetRaisedException(tstate, value); Py_XDECREF(type); Py_XDECREF(traceback); } @@ -425,24 +425,24 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb) PyObject * -_PyErr_Fetch1(PyThreadState *tstate) { +_PyErr_GetRaisedException(PyThreadState *tstate) { PyObject *exc = tstate->current_exception; tstate->current_exception = NULL; return exc; } PyObject * -PyErr_Fetch1(void) +PyErr_GetRaisedException(void) { PyThreadState *tstate = _PyThreadState_GET(); - return _PyErr_Fetch1(tstate); + return _PyErr_GetRaisedException(tstate); } void _PyErr_Fetch(PyThreadState *tstate, PyObject **p_type, PyObject **p_value, PyObject **p_traceback) { - PyObject *exc = _PyErr_Fetch1(tstate); + PyObject *exc = _PyErr_GetRaisedException(tstate); *p_value = exc; if (exc == NULL) { *p_type = NULL; @@ -624,7 +624,7 @@ _PyErr_ChainExceptions(PyObject *typ, PyObject *val, PyObject *tb) } } -/* Like PyErr_Restore1(), but if an exception is already set, +/* 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 @@ -637,12 +637,12 @@ _PyErr_ChainExceptions1(PyObject *exc) } PyThreadState *tstate = _PyThreadState_GET(); if (_PyErr_Occurred(tstate)) { - PyObject *exc2 = _PyErr_Fetch1(tstate); + PyObject *exc2 = _PyErr_GetRaisedException(tstate); PyException_SetContext(exc2, exc); - _PyErr_Restore1(tstate, exc2); + _PyErr_SetRaisedException(tstate, exc2); } else { - _PyErr_Restore1(tstate, exc); + _PyErr_SetRaisedException(tstate, exc); } } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index e98ef6ec07f260..50d83547fb06fa 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1019,7 +1019,7 @@ Py_DECREF(exc_value); } else { - _PyErr_Restore1(tstate, Py_NewRef(exc_value)); + _PyErr_SetRaisedException(tstate, Py_NewRef(exc_value)); goto exception_unwind; } STACK_SHRINK(2); diff --git a/Python/initconfig.c b/Python/initconfig.c index 9843de207553ba..deec805a6b1ca4 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -3143,7 +3143,7 @@ init_dump_ascii_wstr(const wchar_t *str) void _Py_DumpPathConfig(PyThreadState *tstate) { - PyObject *exc = _PyErr_Fetch1(tstate); + PyObject *exc = _PyErr_GetRaisedException(tstate); PySys_WriteStderr("Python path configuration:\n"); @@ -3201,5 +3201,5 @@ _Py_DumpPathConfig(PyThreadState *tstate) PySys_WriteStderr(" ]\n"); } - _PyErr_Restore1(tstate, exc); + _PyErr_SetRaisedException(tstate, exc); } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 762188a13a2f28..6e81ef92b67f70 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -66,11 +66,11 @@ _PySys_GetAttr(PyThreadState *tstate, PyObject *name) if (sd == NULL) { return NULL; } - PyObject *exc = _PyErr_Fetch1(tstate); + 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_Restore1(tstate, exc); + _PyErr_SetRaisedException(tstate, exc); return value; } @@ -3706,7 +3706,7 @@ sys_format(PyObject *key, FILE *fp, const char *format, va_list va) const char *utf8; PyThreadState *tstate = _PyThreadState_GET(); - PyObject *error = _PyErr_Fetch1(tstate); + PyObject *error = _PyErr_GetRaisedException(tstate); file = _PySys_GetAttr(tstate, key); message = PyUnicode_FromFormatV(format, va); if (message != NULL) { @@ -3718,7 +3718,7 @@ sys_format(PyObject *key, FILE *fp, const char *format, va_list va) } Py_DECREF(message); } - _PyErr_Restore1(tstate, error); + _PyErr_SetRaisedException(tstate, error); } void diff --git a/Python/traceback.c b/Python/traceback.c index 6482a8bd9d7e35..31b85e77575efa 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -267,7 +267,7 @@ void _PyTraceback_Add(const char *funcname, const char *filename, int lineno) /* 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. */ - PyObject *exc = _PyErr_Fetch1(tstate); + PyObject *exc = _PyErr_GetRaisedException(tstate); globals = PyDict_New(); if (!globals) @@ -284,7 +284,7 @@ void _PyTraceback_Add(const char *funcname, const char *filename, int lineno) goto error; frame->f_lineno = lineno; - _PyErr_Restore1(tstate, exc); + _PyErr_SetRaisedException(tstate, exc); PyTraceBack_Here(frame); Py_DECREF(frame); return; From 1ae8eda968f786af116a2fcfc619cc39d0ae84e7 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 7 Feb 2023 10:55:39 +0000 Subject: [PATCH 10/13] Fix up news entry --- ...023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst b/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst index 3f731fa7c3cb79..fc694f6e051b53 100644 --- a/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst +++ b/Misc/NEWS.d/next/C API/2023-02-06-16-14-30.gh-issue-101578.PW5fA9.rst @@ -1,5 +1,13 @@ -Deprecate the three arguments forms of :cfunc:`PyErr_Fetch` and -:cfunc:`PyErr_Restore` and replace them with one argument forms that take -and return the exception object. Also add :cfunc:`PyException_GetArgs` and -:cfunc:`PyException_SetArgs` as convenience functions to help dealing with +Add new C-API functions for saving and restoring the current exception: +``PyErr_GetRaisedException`` and ``PyErr_SetRaisedException``. +These functions take and return a single exception rather than +the triple of ``PyErr_Fetch`` and ``PyErr_Restore``. +This is less error prone and a bit more efficient. + +The three arguments forms of saving and restoring the +current exception: ``PyErr_Fetch`` and ``PyErr_Restore`` +are deprecated. + +Also add ``PyException_GetArgs`` and ``PyException_SetArgs`` +as convenience functions to help dealing with exceptions in the C API. From fe6c34f661eb5d097014acabe6cef4fc3b9b92a9 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 7 Feb 2023 12:38:43 +0000 Subject: [PATCH 11/13] Update stable ABI. --- Doc/data/stable_abi.dat | 4 ++++ Lib/test/test_stable_abi_ctypes.py | 4 ++++ Misc/stable_abi.toml | 9 +++++++++ PC/python3dll.c | 4 ++++ Python/errors.c | 6 ++++++ 5 files changed, 27 insertions(+) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 53895bbced8408..68ab0b5061f434 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -139,6 +139,7 @@ function,PyErr_Format,3.2,, function,PyErr_FormatV,3.5,, function,PyErr_GetExcInfo,3.7,, function,PyErr_GetHandledException,3.11,, +function,PyErr_GetRaisedException,3.12,, function,PyErr_GivenExceptionMatches,3.2,, function,PyErr_NewException,3.2,, function,PyErr_NewExceptionWithDoc,3.2,, @@ -168,6 +169,7 @@ function,PyErr_SetInterrupt,3.2,, function,PyErr_SetInterruptEx,3.10,, function,PyErr_SetNone,3.2,, function,PyErr_SetObject,3.2,, +function,PyErr_SetRaisedException,3.12,, function,PyErr_SetString,3.2,, function,PyErr_SyntaxLocation,3.2,, function,PyErr_SyntaxLocationEx,3.7,, @@ -266,9 +268,11 @@ var,PyExc_Warning,3.2,, var,PyExc_WindowsError,3.7,on Windows, var,PyExc_ZeroDivisionError,3.2,, function,PyExceptionClass_Name,3.8,, +function,PyException_GetArgs,3.12,, function,PyException_GetCause,3.2,, function,PyException_GetContext,3.2,, function,PyException_GetTraceback,3.2,, +function,PyException_SetArgs,3.12,, function,PyException_SetCause,3.2,, function,PyException_SetContext,3.2,, function,PyException_SetTraceback,3.2,, diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 67c653428a6dee..e77c1c8409880d 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -172,6 +172,7 @@ def test_windows_feature_macros(self): "PyErr_FormatV", "PyErr_GetExcInfo", "PyErr_GetHandledException", + "PyErr_GetRaisedException", "PyErr_GivenExceptionMatches", "PyErr_NewException", "PyErr_NewExceptionWithDoc", @@ -195,6 +196,7 @@ def test_windows_feature_macros(self): "PyErr_SetInterruptEx", "PyErr_SetNone", "PyErr_SetObject", + "PyErr_SetRaisedException", "PyErr_SetString", "PyErr_SyntaxLocation", "PyErr_SyntaxLocationEx", @@ -292,9 +294,11 @@ def test_windows_feature_macros(self): "PyExc_Warning", "PyExc_ZeroDivisionError", "PyExceptionClass_Name", + "PyException_GetArgs", "PyException_GetCause", "PyException_GetContext", "PyException_GetTraceback", + "PyException_SetArgs", "PyException_SetCause", "PyException_SetContext", "PyException_SetTraceback", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index c716f403d638ac..21ff9616133445 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2333,6 +2333,15 @@ added = '3.12' [function.PyVectorcall_Call] added = '3.12' +[function.PyErr_GetRaisedException] + added = '3.12' +[function.PyErr_SetRaisedException] + added = '3.12' +[function.PyException_GetArgs] + added = '3.12' +[function.PyException_SetArgs] + added = '3.12' + [typedef.vectorcallfunc] added = '3.12' [function.PyObject_Vectorcall] diff --git a/PC/python3dll.c b/PC/python3dll.c index 931f316bb99843..e300819365756e 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -198,6 +198,7 @@ EXPORT_FUNC(PyErr_Format) EXPORT_FUNC(PyErr_FormatV) EXPORT_FUNC(PyErr_GetExcInfo) EXPORT_FUNC(PyErr_GetHandledException) +EXPORT_FUNC(PyErr_GetRaisedException) EXPORT_FUNC(PyErr_GivenExceptionMatches) EXPORT_FUNC(PyErr_NewException) EXPORT_FUNC(PyErr_NewExceptionWithDoc) @@ -227,6 +228,7 @@ EXPORT_FUNC(PyErr_SetInterrupt) EXPORT_FUNC(PyErr_SetInterruptEx) EXPORT_FUNC(PyErr_SetNone) EXPORT_FUNC(PyErr_SetObject) +EXPORT_FUNC(PyErr_SetRaisedException) EXPORT_FUNC(PyErr_SetString) EXPORT_FUNC(PyErr_SyntaxLocation) EXPORT_FUNC(PyErr_SyntaxLocationEx) @@ -255,9 +257,11 @@ EXPORT_FUNC(PyEval_ReleaseThread) EXPORT_FUNC(PyEval_RestoreThread) EXPORT_FUNC(PyEval_SaveThread) EXPORT_FUNC(PyEval_ThreadsInitialized) +EXPORT_FUNC(PyException_GetArgs) EXPORT_FUNC(PyException_GetCause) EXPORT_FUNC(PyException_GetContext) EXPORT_FUNC(PyException_GetTraceback) +EXPORT_FUNC(PyException_SetArgs) EXPORT_FUNC(PyException_SetCause) EXPORT_FUNC(PyException_SetContext) EXPORT_FUNC(PyException_SetTraceback) diff --git a/Python/errors.c b/Python/errors.c index 5ad6418c117ff4..7e125d53779f67 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -73,6 +73,12 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) _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) From 566f6e1ba3675cd077e0fdd2b2ae25aa8a73938c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 7 Feb 2023 15:46:54 +0000 Subject: [PATCH 12/13] Restore behavior of PyErr_Restore, and add tests. --- Lib/test/test_capi/test_misc.py | 39 ++++++++++++ Modules/_testcapimodule.c | 39 +++++++++++- Python/errors.c | 106 ++++++++++++++++++-------------- 3 files changed, 137 insertions(+), 47 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index dace37c362e569..cbf2dba330f56f 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1550,5 +1550,44 @@ def func2(x=None): self.do_test(func2) +class Test_ErrSetAndRestore(unittest.TestCase): + + def test_err_set_raised(self): + with self.assertRaises(ValueError): + _testcapi.err_set_raised(ValueError()) + v = ValueError() + try: + _testcapi.err_set_raised(v) + except ValueError as ex: + self.assertIs(v, ex) + + def test_err_restore(self): + with self.assertRaises(ValueError): + _testcapi.err_restore(ValueError) + with self.assertRaises(ValueError): + _testcapi.err_restore(ValueError, 1) + with self.assertRaises(ValueError): + _testcapi.err_restore(ValueError, 1, None) + with self.assertRaises(ValueError): + _testcapi.err_restore(ValueError, ValueError()) + try: + _testcapi.err_restore(KeyError, "hi") + except KeyError as k: + self.assertEqual("hi", k.args[0]) + try: + 1/0 + except Exception as e: + tb = e.__traceback__ + with self.assertRaises(ValueError): + _testcapi.err_restore(ValueError, 1, tb) + with self.assertRaises(TypeError): + _testcapi.err_restore(ValueError, 1, 0) + try: + _testcapi.err_restore(ValueError, 1, tb) + except ValueError as v: + self.assertEqual(1, v.args[0]) + self.assertIs(tb, v.__traceback__.tb_next) + + if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 46c025bf53404a..bd539980b82c95 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2149,7 +2149,7 @@ dict_get_version(PyObject *self, PyObject *args) return NULL; _Py_COMP_DIAG_PUSH - _Py_COMP_DIAG_IGNORE_DEPR_DECLS + _Py_COMP_DIAG_IGNORE_DEPR_DECLS version = dict->ma_version_tag; _Py_COMP_DIAG_POP @@ -3244,6 +3244,41 @@ function_set_kw_defaults(PyObject *self, PyObject *args) Py_RETURN_NONE; } +static PyObject * +err_set_raised(PyObject *self, PyObject *exc) +{ + Py_INCREF(exc); + PyErr_SetRaisedException(exc); + assert(PyErr_Occurred()); + return NULL; +} + +static PyObject * +err_restore(PyObject *self, PyObject *args) { + PyObject *type = NULL, *value = NULL, *traceback = NULL; + switch(PyTuple_Size(args)) { + case 3: + traceback = PyTuple_GetItem(args, 2); + Py_INCREF(traceback); + /* fall through */ + case 2: + value = PyTuple_GetItem(args, 1); + Py_INCREF(value); + /* fall through */ + case 1: + type = PyTuple_GetItem(args, 0); + Py_INCREF(type); + break; + default: + PyErr_SetString(PyExc_TypeError, + "wrong number of arguments"); + return NULL; + } + PyErr_Restore(type, value, traceback); + assert(PyErr_Occurred()); + return NULL; +} + static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *); static PyMethodDef TestMethods[] = { @@ -3392,6 +3427,8 @@ static PyMethodDef TestMethods[] = { {"function_set_defaults", function_set_defaults, METH_VARARGS, NULL}, {"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL}, {"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL}, + {"err_set_raised", err_set_raised, METH_O, NULL}, + {"err_restore", err_restore, METH_VARARGS, NULL}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/errors.c b/Python/errors.c index 7e125d53779f67..ce2085de946778 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -35,35 +35,75 @@ _PyErr_SetRaisedException(PyThreadState *tstate, PyObject *exc) Py_XDECREF(old_exc); } +static PyObject* +_PyErr_CreateException(PyObject *exception_type, PyObject *value) +{ + PyObject *exc; + + if (value == NULL || value == Py_None) { + exc = _PyObject_CallNoArgs(exception_type); + } + else if (PyTuple_Check(value)) { + exc = PyObject_Call(exception_type, value, NULL); + } + else { + exc = PyObject_CallOneArg(exception_type, value); + } + + if (exc != NULL && !PyExceptionInstance_Check(exc)) { + PyErr_Format(PyExc_TypeError, + "calling %R should have returned an instance of " + "BaseException, not %s", + exception_type, Py_TYPE(exc)->tp_name); + Py_CLEAR(exc); + } + + return exc; +} + void _PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *traceback) { -#ifdef Py_DEBUG - /* Check that we are being passed a normalized exception. - * - * Exceptions are normalized if all NULL, - * or if curexc_type = Py_TYPE(curexc_value) and - * curexc_traceback = curexc_value->traceback - * and both type and traceback are valid */ - if (value == NULL) { - assert(type == NULL); + if (type == NULL) { + assert(value == NULL); assert(traceback == NULL); + _PyErr_SetRaisedException(tstate, NULL); + return; } - else { - assert(PyExceptionClass_Check(type)); - assert(type == (PyObject *)Py_TYPE(value)); + assert(PyExceptionClass_Check(type)); + if (value != NULL && type == (PyObject *)Py_TYPE(value)) { + /* Already normalized */ assert(((PyBaseExceptionObject *)value)->traceback != Py_None); - assert(traceback == ((PyBaseExceptionObject *)value)->traceback || - (traceback == Py_None && - ((PyBaseExceptionObject *)value)->traceback == NULL) - ); } -#endif - + 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_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_XDECREF(type); - Py_XDECREF(traceback); + Py_DECREF(type); } void @@ -94,32 +134,6 @@ _PyErr_GetTopmostException(PyThreadState *tstate) return exc_info; } -static PyObject* -_PyErr_CreateException(PyObject *exception_type, PyObject *value) -{ - PyObject *exc; - - if (value == NULL || value == Py_None) { - exc = _PyObject_CallNoArgs(exception_type); - } - else if (PyTuple_Check(value)) { - exc = PyObject_Call(exception_type, value, NULL); - } - else { - exc = PyObject_CallOneArg(exception_type, value); - } - - if (exc != NULL && !PyExceptionInstance_Check(exc)) { - PyErr_Format(PyExc_TypeError, - "calling %R should have returned an instance of " - "BaseException, not %s", - exception_type, Py_TYPE(exc)->tp_name); - Py_CLEAR(exc); - } - - return exc; -} - void _PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value) { From 99f0880e0d72075850c9abfe91e9a9fd3c8312df Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 7 Feb 2023 18:32:55 +0000 Subject: [PATCH 13/13] Fix refleaks --- Modules/_testcapi/heaptype.c | 6 ++++-- Python/errors.c | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index a6f5a70ae4c333..39639f7ed048f2 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -119,6 +119,7 @@ test_from_spec_invalid_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED( PyObject *exc = NULL; PyObject *result = NULL; PyObject *message = NULL; + PyObject *args = NULL; metaclass_a = PyType_FromSpecWithBases(&MinimalMetaclass_spec, (PyObject*)&PyType_Type); if (metaclass_a == NULL) { @@ -156,13 +157,13 @@ test_from_spec_invalid_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED( // Assert that the correct exception was raised if (PyErr_ExceptionMatches(PyExc_TypeError)) { exc = PyErr_GetRaisedException(); - PyObject *args = PyException_GetArgs(exc); + args = PyException_GetArgs(exc); if (!PyTuple_Check(args) || PyTuple_Size(args) != 1) { PyErr_SetString(PyExc_AssertionError, "TypeError args are not a one-tuple"); goto finally; } - PyObject *message = Py_NewRef(PyTuple_GET_ITEM(args, 0)); + message = Py_NewRef(PyTuple_GET_ITEM(args, 0)); meta_error_string = PyUnicode_FromString("metaclass conflict:"); if (meta_error_string == NULL) { goto finally; @@ -188,6 +189,7 @@ test_from_spec_invalid_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED( Py_XDECREF(message); Py_XDECREF(class_a); Py_XDECREF(class_b); + Py_XDECREF(args); return result; } diff --git a/Python/errors.c b/Python/errors.c index ce2085de946778..f573bed3d63ef0 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -94,6 +94,7 @@ _PyErr_Restore(PyThreadState *tstate, PyObject *type, PyObject *value, } else { PyErr_SetString(PyExc_TypeError, "traceback must be a Traceback or None"); + Py_XDECREF(value); Py_DECREF(type); Py_XDECREF(traceback); return;