8000 GH-101578: Normalize the current exception by markshannon · Pull Request #101607 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

GH-101578: Normalize the current exception #101607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Feb 8, 2023
Merged
62 changes: 61 additions & 1 deletion Doc/c-api/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -422,6 +424,49 @@ Querying the error indicator
}


.. c 8000 :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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?? this is PyErr_Restore1


Set the error indicator to ``exc``.
Copy link
8000 Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it's in the wrong place.

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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Include/cpython/pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__.
Expand Down
4 changes: 1 addition & 3 deletions Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down
11 changes: 10 additions & 1 deletion Include/internal/pycore_pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -37,10 +40,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,
Expand Down
6 changes: 6 additions & 0 deletions Include/pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand Down Expand Up @@ -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) \
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down Expand Up @@ -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:
Expand All @@ -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)


Expand Down
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 13 additions & 9 deletions Modules/_testcapi/heaptype.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
10000 Expand All @@ -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;
Expand Down
5 changes: 2 additions & 3 deletions Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
6 changes: 3 additions & 3 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
0