From f17762f93b1f882554b6fe12c8271f907aebf6ad Mon Sep 17 00:00:00 2001 From: Armin Rigo Date: Fri, 19 Feb 2021 09:04:07 +0100 Subject: #492 Python 3.8 adds support for sys.unraisablehook(), but also a new _PyErr_WriteUnraisableMsg() that calls it and is slightly more flexible in displaying the error message. Use that --- c/_cffi_backend.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ c/test_c.py | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c index 525dec4..6652803 100644 --- a/c/_cffi_backend.c +++ b/c/_cffi_backend.c @@ -180,6 +180,10 @@ # define Py_SET_REFCNT(obj, val) (Py_REFCNT(obj) = (val)) #endif +#if PY_VERSION_HEX >= 0x03080000 +# define USE_WRITEUNRAISABLEMSG +#endif + /************************************************************/ /* base type flag: exactly one of the following: */ @@ -6066,6 +6070,43 @@ static void _my_PyErr_WriteUnraisable(PyObject *t, PyObject *v, PyObject *tb, char *extra_error_line) { /* like PyErr_WriteUnraisable(), but write a full traceback */ +#ifdef USE_WRITEUNRAISABLEMSG + + /* PyErr_WriteUnraisable actually writes the full traceback anyway + from Python 3.4, but we can't really get the formatting of the + custom text to be what we want. We can do better from Python + 3.8 by calling the new _PyErr_WriteUnraisableMsg(). + Luckily it's also Python 3.8 that adds new functionality that + people might want: the new sys.unraisablehook(). + */ + PyObject *s; + int first_char; + assert(objdescr != NULL && objdescr[0] != 0); /* non-empty */ + first_char = objdescr[0]; + if (first_char >= 'A' && first_char <= 'Z') + first_char += 'a' - 'A'; /* lower() the very first character */ + if (extra_error_line == NULL) + extra_error_line = ""; + + if (obj != NULL) + s = PyUnicode_FromFormat("%c%s%R%s", + first_char, objdescr + 1, obj, extra_error_line); + else + s = PyUnicode_FromFormat("%c%s%s", + first_char, objdescr + 1, extra_error_line); + + PyErr_Restore(t, v, tb); + if (s != NULL) { + _PyErr_WriteUnraisableMsg(PyText_AS_UTF8(s), NULL); + Py_DECREF(s); + } + else + PyErr_WriteUnraisable(obj); /* best effort */ + PyErr_Clear(); + +#else + + /* version for Python 2.7 and < 3.8 */ PyObject *f; #if PY_MAJOR_VERSION >= 3 /* jump through hoops to ensure the tb is attached to v, on Python 3 */ @@ -6090,6 +6131,8 @@ static void _my_PyErr_WriteUnraisable(PyObject *t, PyObject *v, PyObject *tb, Py_XDECREF(t); Py_XDECREF(v); Py_XDECREF(tb); + +#endif } static void general_invoke_callback(int decode_args_from_libffi, @@ -6139,7 +6182,11 @@ static void general_invoke_callback(int decode_args_from_libffi, goto error; if (convert_from_object_fficallback(result, SIGNATURE(1), py_res, decode_args_from_libffi) < 0) { +#ifdef USE_WRITEUNRAISABLEMSG + extra_error_line = ", trying to convert the result back to C"; +#else extra_error_line = "Trying to convert the result back to C:\n"; +#endif goto error; } done: @@ -6191,10 +6238,16 @@ static void general_invoke_callback(int decode_args_from_libffi, _my_PyErr_WriteUnraisable(exc1, val1, tb1, "From cffi callback ", py_ob, extra_error_line); +#ifdef USE_WRITEUNRAISABLEMSG + _my_PyErr_WriteUnraisable(exc2, val2, tb2, + "during handling of the above exception by 'onerror'", + NULL, NULL); +#else extra_error_line = ("\nDuring the call to 'onerror', " "another exception occurred:\n\n"); _my_PyErr_WriteUnraisable(exc2, val2, tb2, NULL, NULL, extra_error_line); +#endif _cffi_stop_error_capture(ecap); } } diff --git a/c/test_c.py b/c/test_c.py index 69ad15e..788500e 100644 --- a/c/test_c.py +++ b/c/test_c.py @@ -1331,7 +1331,9 @@ def test_callback_exception(): except ImportError: import io as cStringIO # Python 3 import linecache - def matches(istr, ipattern): + def matches(istr, ipattern, ipattern38): + if sys.version_info >= (3, 8): + ipattern = ipattern38 str, pattern = istr, ipattern while '$' in pattern: i = pattern.index('$') @@ -1375,6 +1377,14 @@ Traceback (most recent call last): File "$", line $, in check_value $ ValueError: 42 +""", """\ +Exception ignored from cffi callback : +Traceback (most recent call last): + File "$", line $, in Zcb1 + $ + File "$", line $, in check_value + $ +ValueError: 42 """) sys.stderr = cStringIO.StringIO() bigvalue = 20000 @@ -1383,6 +1393,12 @@ ValueError: 42 From cffi callback : Trying to convert the result back to C: OverflowError: integer 60000 does not fit 'short' +""", """\ +Exception ignored from cffi callback , trying to convert the result back to C: +Traceback (most recent call last): + File "$", line $, in test_callback_exception + $ +OverflowError: integer 60000 does not fit 'short' """) sys.stderr = cStringIO.StringIO() bigvalue = 20000 @@ -1419,6 +1435,17 @@ OverflowError: integer 60000 does not fit 'short' During the call to 'onerror', another exception occurred: +TypeError: $integer$ +""", """\ +Exception ignored from cffi callback , trying to convert the result back to C: +Traceback (most recent call last): + File "$", line $, in test_callback_exception + $ +OverflowError: integer 60000 does not fit 'short' +Exception ignored during handling of the above exception by 'onerror': +Traceback (most recent call last): + File "$", line $, in test_callback_exception + $ TypeError: $integer$ """) # @@ -1432,6 +1459,17 @@ OverflowError: integer 60000 does not fit 'short' During the call to 'onerror', another exception occurred: +Traceback (most recent call last): + File "$", line $, in oops + $ +AttributeError: 'str' object has no attribute 'append' +""", """\ +Exception ignored from cffi callback , trying to convert the result back to C: +Traceback (most recent call last): + File "$", line $, in test_callback_exception + $ +OverflowError: integer 60000 does not fit 'short' +Exception ignored during handling of the above exception by 'onerror': Traceback (most recent call last): File "$", line $, in oops $ -- cgit v1.2.1