]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-102594: PyErr_SetObject adds note to exception raised on normalization error ...
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>
Thu, 16 Mar 2023 10:16:01 +0000 (10:16 +0000)
committerGitHub <noreply@github.com>
Thu, 16 Mar 2023 10:16:01 +0000 (10:16 +0000)
Include/cpython/pyerrors.h
Lib/test/test_capi/test_exceptions.py
Misc/NEWS.d/next/Core and Builtins/2023-03-14-00-11-46.gh-issue-102594.BjU-m2.rst [new file with mode: 0644]
Modules/_testcapi/exceptions.c
Objects/exceptions.c
Python/errors.c

index 0d9cc9922f73687b1a9d99d431c2cd88d651ec75..d0300f6ee56a25444ead06f07a3676a8f26fdb6b 100644 (file)
@@ -112,6 +112,10 @@ PyAPI_FUNC(PyObject *) _PyErr_FormatFromCause(
 
 /* In exceptions.c */
 
+PyAPI_FUNC(int) _PyException_AddNote(
+     PyObject *exc,
+     PyObject *note);
+
 /* Helper that attempts to replace the current exception with one of the
  * same type but with a prefix added to the exception text. The resulting
  * exception description looks like:
index 55f131699a25670b3fd8f2eb6bfb59787ee42b4b..b1c1a61e20685e88349fae9ed72cad759764febb 100644 (file)
@@ -169,5 +169,25 @@ class Test_ErrSetAndRestore(unittest.TestCase):
         with self.assertRaises(ZeroDivisionError) as e:
             _testcapi.exc_set_object(Broken, Broken())
 
+    def test_set_object_and_fetch(self):
+        class Broken(Exception):
+            def __init__(self, *arg):
+                raise ValueError("Broken __init__")
+
+        exc = _testcapi.exc_set_object_fetch(Broken, 'abcd')
+        self.assertIsInstance(exc, ValueError)
+        self.assertEqual(exc.__notes__[0],
+                         "Normalization failed: type=Broken args='abcd'")
+
+        class BadArg:
+            def __repr__(self):
+                raise TypeError('Broken arg type')
+
+        exc = _testcapi.exc_set_object_fetch(Broken, BadArg())
+        self.assertIsInstance(exc, ValueError)
+        self.assertEqual(exc.__notes__[0],
+                         'Normalization failed: type=Broken args=<unknown>')
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-03-14-00-11-46.gh-issue-102594.BjU-m2.rst b/Misc/NEWS.d/next/Core and Builtins/2023-03-14-00-11-46.gh-issue-102594.BjU-m2.rst
new file mode 100644 (file)
index 0000000..0b95b5e
--- /dev/null
@@ -0,0 +1 @@
+Add note to exception raised in ``PyErr_SetObject`` when normalization fails.
index a0575213987ffcfb715ed468fd61a8ed118fc73d..c64b823663c32f1aea0e4f7786a28d7a65e5c83c 100644 (file)
@@ -92,6 +92,26 @@ exc_set_object(PyObject *self, PyObject *args)
     return NULL;
 }
 
+static PyObject *
+exc_set_object_fetch(PyObject *self, PyObject *args)
+{
+    PyObject *exc;
+    PyObject *obj;
+    PyObject *type;
+    PyObject *value;
+    PyObject *tb;
+
+    if (!PyArg_ParseTuple(args, "OO:exc_set_object", &exc, &obj)) {
+        return NULL;
+    }
+
+    PyErr_SetObject(exc, obj);
+    PyErr_Fetch(&type, &value, &tb);
+    Py_XDECREF(type);
+    Py_XDECREF(tb);
+    return value;
+}
+
 static PyObject *
 raise_exception(PyObject *self, PyObject *args)
 {
@@ -262,6 +282,7 @@ static PyMethodDef test_methods[] = {
     {"make_exception_with_doc", _PyCFunction_CAST(make_exception_with_doc),
      METH_VARARGS | METH_KEYWORDS},
     {"exc_set_object",          exc_set_object,                  METH_VARARGS},
+    {"exc_set_object_fetch",    exc_set_object_fetch,            METH_VARARGS},
     {"raise_exception",         raise_exception,                 METH_VARARGS},
     {"raise_memoryerror",       raise_memoryerror,               METH_NOARGS},
     {"set_exc_info",            test_set_exc_info,               METH_VARARGS},
index c6fb6a3f19b2d06d957891fff81b637125629218..d69f7400ca6042cbc63801a6e0e92b768e66f03c 100644 (file)
@@ -3749,6 +3749,21 @@ _PyExc_Fini(PyInterpreterState *interp)
     _PyExc_FiniTypes(interp);
 }
 
+int
+_PyException_AddNote(PyObject *exc, PyObject *note)
+{
+    if (!PyExceptionInstance_Check(exc)) {
+        PyErr_Format(PyExc_TypeError,
+                     "exc must be an exception, not '%s'",
+                     Py_TYPE(exc)->tp_name);
+        return -1;
+    }
+    PyObject *r = BaseException_add_note(exc, note);
+    int res = r == NULL ? -1 : 0;
+    Py_XDECREF(r);
+    return res;
+}
+
 /* Helper to do the equivalent of "raise X from Y" in C, but always using
  * the current exception rather than passing one in.
  *
index bbf6d397ce8097c9b7f9ee1ada833527bbd507f9..bdcbac317eb9ee862350cd94985f4ed7ed29f8ff 100644 (file)
@@ -135,6 +135,28 @@ _PyErr_GetTopmostException(PyThreadState *tstate)
     return exc_info;
 }
 
+static PyObject *
+get_normalization_failure_note(PyThreadState *tstate, PyObject *exception, PyObject *value)
+{
+    PyObject *args = PyObject_Repr(value);
+    if (args == NULL) {
+        _PyErr_Clear(tstate);
+        args = PyUnicode_FromFormat("<unknown>");
+    }
+    PyObject *note;
+    const char *tpname = ((PyTypeObject*)exception)->tp_name;
+    if (args == NULL) {
+        _PyErr_Clear(tstate);
+        note = PyUnicode_FromFormat("Normalization failed: type=%s", tpname);
+    }
+    else {
+        note = PyUnicode_FromFormat("Normalization failed: type=%s args=%S",
+                                    tpname, args);
+        Py_DECREF(args);
+    }
+    return note;
+}
+
 void
 _PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value)
 {
@@ -160,19 +182,27 @@ _PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value)
     Py_XINCREF(value);
     if (!is_subclass) {
         /* 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);
+        PyObject *fixed_value = _PyErr_CreateException(exception, value);
         if (fixed_value == NULL) {
+            PyObject *exc = _PyErr_GetRaisedException(tstate);
+            assert(PyExceptionInstance_Check(exc));
+
+            PyObject *note = get_normalization_failure_note(tstate, exception, value);
+            Py_XDECREF(value);
+            if (note != NULL) {
+                /* ignore errors in _PyException_AddNote - they will be overwritten below */
+                _PyException_AddNote(exc, note);
+                Py_DECREF(note);
+            }
+            _PyErr_SetRaisedException(tstate, exc);
             return;
         }
-
-        value = fixed_value;
+        Py_XSETREF(value, fixed_value);
     }
 
     exc_value = _PyErr_GetTopmostException(tstate)->exc_value;