Skip to content
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

gh-102755: Add PyErr_DisplayException(exc) #102756

Merged
merged 5 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Doc/c-api/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ Printing and clearing
An exception must be set when calling this function.
.. c:function: void PyErr_DisplayException(PyObject *exc)
Print the standard traceback display of ``exc`` to ``sys.stderr``, including
chained exceptions and notes.
.. versionadded:: 3.12
Raising exceptions
==================
Expand Down
1 change: 1 addition & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,10 @@ New Features
the :attr:`~BaseException.args` passed to the exception's constructor.
(Contributed by Mark Shannon in :gh:`101578`.)

* Add :c:func:`PyErr_DisplayException`, which takes an exception instance,
to replace the legacy-api :c:func:`PyErr_Display`. (Contributed by
Irit Katriel in :gh:`102755`).

Porting to Python 3.12
----------------------

Expand Down Expand Up @@ -1077,6 +1081,9 @@ Deprecated
:c:func:`PyErr_SetRaisedException` instead.
(Contributed by Mark Shannon in :gh:`101578`.)

* :c:func:`PyErr_Display` is deprecated. Use :c:func:`PyErr_DisplayException`
instead. (Contributed by Irit Katriel in :gh:`102755`).


Removed
-------
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ PyAPI_FUNC(PyObject*) _PyErr_WriteUnraisableDefaultHook(PyObject *unraisable);
PyAPI_FUNC(void) _PyErr_Print(PyThreadState *tstate);
PyAPI_FUNC(void) _PyErr_Display(PyObject *file, PyObject *exception,
PyObject *value, PyObject *tb);
PyAPI_FUNC(void) _PyErr_DisplayException(PyObject *file, PyObject *exc);

PyAPI_FUNC(void) _PyThreadState_DeleteCurrent(PyThreadState *tstate);

Expand Down
1 change: 1 addition & 0 deletions Include/pythonrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ PyAPI_FUNC(PyObject *) Py_CompileString(const char *, const char *, int);
PyAPI_FUNC(void) PyErr_Print(void);
PyAPI_FUNC(void) PyErr_PrintEx(int);
PyAPI_FUNC(void) PyErr_Display(PyObject *, PyObject *, PyObject *);
PyAPI_FUNC(void) PyErr_DisplayException(PyObject *);


/* Stuff with no proper home (yet) */
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :c:func:`PyErr_DisplayException` which takes just an exception instance,
to replace the legacy :c:func:`PyErr_Display` which takes the ``(typ, exc,
tb)`` triplet.
2 changes: 2 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@
added = '3.2'
[function.PyErr_Display]
added = '3.2'
[function.PyErr_DisplayException]
added = '3.12'
[function.PyErr_ExceptionMatches]
added = '3.2'
[function.PyErr_Fetch]
Expand Down
13 changes: 3 additions & 10 deletions Modules/_testcapi/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,13 @@ err_restore(PyObject *self, PyObject *args) {
static PyObject *
exception_print(PyObject *self, PyObject *args)
{
PyObject *value;
PyObject *tb = NULL;
PyObject *exc;

if (!PyArg_ParseTuple(args, "O:exception_print", &value)) {
if (!PyArg_ParseTuple(args, "O:exception_print", &exc)) {
return NULL;
}

if (PyExceptionInstance_Check(value)) {
tb = PyException_GetTraceback(value);
}

PyErr_Display((PyObject *) Py_TYPE(value), value, tb);
Py_XDECREF(tb);

PyErr_DisplayException(exc);
Py_RETURN_NONE;
}

Expand Down
1 change: 1 addition & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 8 additions & 21 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -2537,41 +2537,28 @@ _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp,
static int
_Py_FatalError_PrintExc(PyThreadState *tstate)
{
PyObject *ferr, *res;
PyObject *exception, *v, *tb;
int has_tb;

_PyErr_Fetch(tstate, &exception, &v, &tb);
if (exception == NULL) {
PyObject *exc = _PyErr_GetRaisedException(tstate);
if (exc == NULL) {
/* No current exception */
return 0;
}

ferr = _PySys_GetAttr(tstate, &_Py_ID(stderr));
PyObject *ferr = _PySys_GetAttr(tstate, &_Py_ID(stderr));
if (ferr == NULL || ferr == Py_None) {
iritkatriel marked this conversation as resolved.
Show resolved Hide resolved
/* sys.stderr is not set yet or set to None,
no need to try to display the exception */
return 0;
}

_PyErr_NormalizeException(tstate, &exception, &v, &tb);
if (tb == NULL) {
tb = Py_NewRef(Py_None);
}
PyException_SetTraceback(v, tb);
if (exception == NULL) {
/* PyErr_NormalizeException() failed */
return 0;
}
PyErr_DisplayException(exc);

has_tb = (tb != Py_None);
PyErr_Display(exception, v, tb);
Py_XDECREF(exception);
Py_XDECREF(v);
PyObject *tb = PyException_GetTraceback(exc);
int has_tb = (tb != NULL) && (tb != Py_None);
Py_XDECREF(tb);
Py_XDECREF(exc);

/* sys.stderr may be buffered: call sys.stderr.flush() */
res = PyObject_CallMethodNoArgs(ferr, &_Py_ID(flush));
PyObject *res = PyObject_CallMethodNoArgs(ferr, &_Py_ID(flush));
if (res == NULL) {
_PyErr_Clear(tstate);
}
Expand Down
87 changes: 41 additions & 46 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -761,39 +761,34 @@ handle_system_exit(void)
static void
_PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars)
{
PyObject *exception, *v, *tb, *hook;

PyObject *typ = NULL, *tb = NULL;
handle_system_exit();

_PyErr_Fetch(tstate, &exception, &v, &tb);
if (exception == NULL) {
PyObject *exc = _PyErr_GetRaisedException(tstate);
if (exc == NULL) {
goto done;
}

_PyErr_NormalizeException(tstate, &exception, &v, &tb);
assert(PyExceptionInstance_Check(exc));
typ = Py_NewRef(Py_TYPE(exc));
tb = PyException_GetTraceback(exc);
if (tb == NULL) {
tb = Py_NewRef(Py_None);
}
PyException_SetTraceback(v, tb);
if (exception == NULL) {
goto done;
}

/* Now we know v != NULL too */
if (set_sys_last_vars) {
if (_PySys_SetAttr(&_Py_ID(last_type), exception) < 0) {
if (_PySys_SetAttr(&_Py_ID(last_type), typ) < 0) {
_PyErr_Clear(tstate);
}
if (_PySys_SetAttr(&_Py_ID(last_value), v) < 0) {
if (_PySys_SetAttr(&_Py_ID(last_value), exc) < 0) {
_PyErr_Clear(tstate);
}
if (_PySys_SetAttr(&_Py_ID(last_traceback), tb) < 0) {
_PyErr_Clear(tstate);
}
}
hook = _PySys_GetAttr(tstate, &_Py_ID(excepthook));
PyObject *hook = _PySys_GetAttr(tstate, &_Py_ID(excepthook));
if (_PySys_Audit(tstate, "sys.excepthook", "OOOO", hook ? hook : Py_None,
exception, v, tb) < 0) {
typ, exc, tb) < 0) {
if (PyErr_ExceptionMatches(PyExc_RuntimeError)) {
PyErr_Clear();
goto done;
Expand All @@ -802,46 +797,34 @@ _PyErr_PrintEx(PyThreadState *tstate, int set_sys_last_vars)
}
if (hook) {
PyObject* stack[3];
PyObject *result;

stack[0] = exception;
stack[1] = v;
stack[0] = typ;
stack[1] = exc;
stack[2] = tb;
result = _PyObject_FastCall(hook, stack, 3);
PyObject *result = _PyObject_FastCall(hook, stack, 3);
if (result == NULL) {
handle_system_exit();

PyObject *exception2, *v2, *tb2;
_PyErr_Fetch(tstate, &exception2, &v2, &tb2);
_PyErr_NormalizeException(tstate, &exception2, &v2, &tb2);
/* It should not be possible for exception2 or v2
to be NULL. However PyErr_Display() can't
tolerate NULLs, so just be safe. */
if (exception2 == NULL) {
exception2 = Py_NewRef(Py_None);
}
if (v2 == NULL) {
v2 = Py_NewRef(Py_None);
}
PyObject *exc2 = _PyErr_GetRaisedException(tstate);
assert(exc2 && PyExceptionInstance_Check(exc2));
fflush(stdout);
PySys_WriteStderr("Error in sys.excepthook:\n");
PyErr_Display(exception2, v2, tb2);
PyErr_DisplayException(exc2);
PySys_WriteStderr("\nOriginal exception was:\n");
PyErr_Display(exception, v, tb);
Py_DECREF(exception2);
Py_DECREF(v2);
Py_XDECREF(tb2);
PyErr_DisplayException(exc);
Py_DECREF(exc2);
}
else {
Py_DECREF(result);
}
Py_XDECREF(result);
}
else {
PySys_WriteStderr("sys.excepthook is missing\n");
PyErr_Display(exception, v, tb);
PyErr_DisplayException(exc);
}

done:
Py_XDECREF(exception);
Py_XDECREF(v);
Py_XDECREF(typ);
Py_XDECREF(exc);
Py_XDECREF(tb);
}

Expand Down Expand Up @@ -1505,18 +1488,20 @@ print_exception_recursive(struct exception_print_context *ctx, PyObject *value)
#define PyErr_MAX_GROUP_DEPTH 10

void
_PyErr_Display(PyObject *file, PyObject *exception, PyObject *value, PyObject *tb)
_PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb)
{
assert(file != NULL && file != Py_None);
if (PyExceptionInstance_Check(value)
&& tb != NULL && PyTraceBack_Check(tb)) {
/* Put the traceback on the exception, otherwise it won't get
displayed. See issue #18776. */
PyObject *cur_tb = PyException_GetTraceback(value);
if (cur_tb == NULL)
if (cur_tb == NULL) {
PyException_SetTraceback(value, tb);
else
}
else {
Py_DECREF(cur_tb);
}
}

struct exception_print_context ctx;
Expand Down Expand Up @@ -1552,7 +1537,7 @@ _PyErr_Display(PyObject *file, PyObject *exception, PyObject *value, PyObject *t
}

void
PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
PyErr_Display(PyObject *unused, PyObject *value, PyObject *tb)
{
PyThreadState *tstate = _PyThreadState_GET();
PyObject *file = _PySys_GetAttr(tstate, &_Py_ID(stderr));
Expand All @@ -1565,10 +1550,20 @@ PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
return;
}
Py_INCREF(file);
_PyErr_Display(file, exception, value, tb);
_PyErr_Display(file, NULL, value, tb);
Py_DECREF(file);
}

void _PyErr_DisplayException(PyObject *file, PyObject *exc)
{
_PyErr_Display(file, NULL, exc, NULL);
}

void PyErr_DisplayException(PyObject *exc)
{
PyErr_Display(NULL, exc, NULL);
}

PyObject *
PyRun_StringFlags(const char *str, int start, PyObject *globals,
PyObject *locals, PyCompilerFlags *flags)
Expand Down
2 changes: 1 addition & 1 deletion Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ sys_excepthook_impl(PyObject *module, PyObject *exctype, PyObject *value,
PyObject *traceback)
/*[clinic end generated code: output=18d99fdda21b6b5e input=ecf606fa826f19d9]*/
{
PyErr_Display(exctype, value, traceback);
PyErr_Display(NULL, value, traceback);
Copy link
Member

Choose a reason for hiding this comment

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

So this will hit the case where PyErr_Display() stuffs the given traceback onto the exception if the latter doesn't already have one? (Else why not use PyErr_DisplayException(), right?)

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly. This is sys.excepthook() which anyone can call so we need to deprecate the case where traceback != value.__traceback__ before we can use PyErr_DisplayException here. (And eventually go through some kind of migration exercise for excepthook's signature, but let's survive the __exit__ discussion first).

Py_RETURN_NONE;
}

Expand Down