implement chained exception tracebacks

patch from Antoine Pitrou #3112
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
index c46e9f4..ad758a6 100644
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -1242,18 +1242,19 @@
 	if (exception == NULL)
 		return;
 	PyErr_NormalizeException(&exception, &v, &tb);
+	tb = tb ? tb : Py_None;
+	PyException_SetTraceback(v, tb);
 	if (exception == NULL)
 		return;
         /* Now we know v != NULL too */
 	if (set_sys_last_vars) {
 		PySys_SetObject("last_type", exception);
 		PySys_SetObject("last_value", v);
-		PySys_SetObject("last_traceback", tb ? tb : Py_None);
+		PySys_SetObject("last_traceback", tb);
 	}
 	hook = PySys_GetObject("excepthook");
 	if (hook) {
-		PyObject *args = PyTuple_Pack(3,
-		    exception, v, tb ? tb : Py_None);
+		PyObject *args = PyTuple_Pack(3, exception, v, tb);
 		PyObject *result = PyEval_CallObject(hook, args);
 		if (result == NULL) {
 			PyObject *exception2, *v2, *tb2;
@@ -1293,12 +1294,164 @@
 	Py_XDECREF(tb);
 }
 
+static void
+print_exception(PyObject *f, PyObject *value)
+{
+	int err = 0;
+	PyObject *type, *tb;
+
+	Py_INCREF(value);
+	fflush(stdout);
+	type = (PyObject *) Py_TYPE(value);
+	tb = PyException_GetTraceback(value);
+	if (tb && tb != Py_None)
+		err = PyTraceBack_Print(tb, f);
+	if (err == 0 &&
+		PyObject_HasAttrString(value, "print_file_and_line"))
+	{
+		PyObject *message;
+		const char *filename, *text;
+		int lineno, offset;
+		if (!parse_syntax_error(value, &message, &filename,
+					&lineno, &offset, &text))
+			PyErr_Clear();
+		else {
+			char buf[10];
+			PyFile_WriteString("  File \"", f);
+			if (filename == NULL)
+				PyFile_WriteString("<string>", f);
+			else
+				PyFile_WriteString(filename, f);
+			PyFile_WriteString("\", line ", f);
+			PyOS_snprintf(buf, sizeof(buf), "%d", lineno);
+			PyFile_WriteString(buf, f);
+			PyFile_WriteString("\n", f);
+			if (text != NULL)
+				print_error_text(f, offset, text);
+			Py_DECREF(value);
+			value = message;
+			/* Can't be bothered to check all those
+			   PyFile_WriteString() calls */
+			if (PyErr_Occurred())
+				err = -1;
+		}
+	}
+	if (err) {
+		/* Don't do anything else */
+	}
+	else {
+		assert(PyExceptionClass_Check(type));
+		PyObject* moduleName;
+		char* className = PyExceptionClass_Name(type);
+		if (className != NULL) {
+			char *dot = strrchr(className, '.');
+			if (dot != NULL)
+				className = dot+1;
+		}
+
+		moduleName = PyObject_GetAttrString(type, "__module__");
+		if (moduleName == NULL || !PyUnicode_Check(moduleName))
+		{
+			Py_DECREF(moduleName);
+			err = PyFile_WriteString("<unknown>", f);
+		}
+		else {
+			char* modstr = PyUnicode_AsString(moduleName);
+			if (modstr && strcmp(modstr, "builtins"))
+			{
+				err = PyFile_WriteString(modstr, f);
+				err += PyFile_WriteString(".", f);
+			}
+			Py_DECREF(moduleName);
+		}
+		if (err == 0) {
+			if (className == NULL)
+				  err = PyFile_WriteString("<unknown>", f);
+			else
+				  err = PyFile_WriteString(className, f);
+		}
+	}
+	if (err == 0 && (value != Py_None)) {
+		PyObject *s = PyObject_Str(value);
+		/* only print colon if the str() of the
+		   object is not the empty string
+		*/
+		if (s == NULL)
+			err = -1;
+		else if (!PyUnicode_Check(s) ||
+			PyUnicode_GetSize(s) != 0)
+			err = PyFile_WriteString(": ", f);
+		if (err == 0)
+		  err = PyFile_WriteObject(s, f, Py_PRINT_RAW);
+		Py_XDECREF(s);
+	}
+	/* try to write a newline in any case */
+	err += PyFile_WriteString("\n", f);
+	Py_XDECREF(tb);
+	Py_DECREF(value);
+	/* If an error happened here, don't show it.
+	   XXX This is wrong, but too many callers rely on this behavior. */
+	if (err != 0)
+		PyErr_Clear();
+}
+
+static const char *cause_message =
+	"\nThe above exception was the direct cause "
+	"of the following exception:\n\n";
+
+static const char *context_message =
+	"\nDuring handling of the above exception, "
+	"another exception occurred:\n\n";
+
+static void
+print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
+{
+	int err = 0, res;
+	PyObject *cause, *context;
+
+	if (seen != NULL) {
+		/* Exception chaining */
+		if (PySet_Add(seen, value) == -1)
+			PyErr_Clear();
+		else if (PyExceptionInstance_Check(value)) {
+			cause = PyException_GetCause(value);
+			context = PyException_GetContext(value);
+			if (cause) {
+				res = PySet_Contains(seen, cause);
+				if (res == -1)
+					PyErr_Clear();
+				if (res == 0) {
+					print_exception_recursive(
+						f, cause, seen);
+					err |= PyFile_WriteString(
+						cause_message, f);
+				}
+			}
+			if (context) {
+				res = PySet_Contains(seen, context);
+				if (res == -1)
+					PyErr_Clear();
+				if (res == 0) {
+					print_exception_recursive(
+						f, context, seen);
+					err |= PyFile_WriteString(
+						context_message, f);
+				}
+			}
+			Py_XDECREF(context);
+			Py_XDECREF(cause);
+		}
+	}
+	print_exception(f, value);
+	if (err != 0)
+		PyErr_Clear();
+}
+
 void
 PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
 {
-	int err = 0;
+	PyObject *seen;
 	PyObject *f = PySys_GetObject("stderr");
-	Py_INCREF(value);
 	if (f == Py_None) {
 		/* pass */
 	}
@@ -1307,97 +1460,15 @@
 		fprintf(stderr, "lost sys.stderr\n");
 	}
 	else {
-		fflush(stdout);
-		if (tb && tb != Py_None)
-			err = PyTraceBack_Print(tb, f);
-		if (err == 0 &&
-		    PyObject_HasAttrString(value, "print_file_and_line"))
-		{
-			PyObject *message;
-			const char *filename, *text;
-			int lineno, offset;
-			if (!parse_syntax_error(value, &message, &filename,
-						&lineno, &offset, &text))
-				PyErr_Clear();
-			else {
-				char buf[10];
-				PyFile_WriteString("  File \"", f);
-				if (filename == NULL)
-					PyFile_WriteString("<string>", f);
-				else
-					PyFile_WriteString(filename, f);
-				PyFile_WriteString("\", line ", f);
-				PyOS_snprintf(buf, sizeof(buf), "%d", lineno);
-				PyFile_WriteString(buf, f);
-				PyFile_WriteString("\n", f);
-				if (text != NULL)
-					print_error_text(f, offset, text);
-				Py_DECREF(value);
-				value = message;
-				/* Can't be bothered to check all those
-				   PyFile_WriteString() calls */
-				if (PyErr_Occurred())
-					err = -1;
-			}
-		}
-		if (err) {
-			/* Don't do anything else */
-		}
-		else if (PyExceptionClass_Check(exception)) {
-			PyObject* moduleName;
-			char* className = PyExceptionClass_Name(exception);
-			if (className != NULL) {
-				char *dot = strrchr(className, '.');
-				if (dot != NULL)
-					className = dot+1;
-			}
-
-			moduleName = PyObject_GetAttrString(exception, "__module__");
-			if (moduleName == NULL || !PyUnicode_Check(moduleName))
-			{
-				Py_DECREF(moduleName);
-				err = PyFile_WriteString("<unknown>", f);
-			}
-			else {
-				char* modstr = PyUnicode_AsString(moduleName);
-				if (modstr && strcmp(modstr, "builtins"))
-				{
-					err = PyFile_WriteString(modstr, f);
-					err += PyFile_WriteString(".", f);
-				}
-				Py_DECREF(moduleName);
-			}
-			if (err == 0) {
-				if (className == NULL)
-				      err = PyFile_WriteString("<unknown>", f);
-				else
-				      err = PyFile_WriteString(className, f);
-			}
-		}
-		else
-			err = PyFile_WriteObject(exception, f, Py_PRINT_RAW);
-		if (err == 0 && (value != Py_None)) {
-			PyObject *s = PyObject_Str(value);
-			/* only print colon if the str() of the
-			   object is not the empty string
-			*/
-			if (s == NULL)
-				err = -1;
-			else if (!PyUnicode_Check(s) ||
-				 PyUnicode_GetSize(s) != 0)
-				err = PyFile_WriteString(": ", f);
-			if (err == 0)
-			  err = PyFile_WriteObject(s, f, Py_PRINT_RAW);
-			Py_XDECREF(s);
-		}
-		/* try to write a newline in any case */
-		err += PyFile_WriteString("\n", f);
+		/* We choose to ignore seen being possibly NULL, and report
+		   at least the main exception (it could be a MemoryError).
+		*/
+		seen = PySet_New(NULL);
+		if (seen == NULL)
+			PyErr_Clear();
+		print_exception_recursive(f, value, seen);
+		Py_XDECREF(seen);
 	}
-	Py_DECREF(value);
-	/* If an error happened here, don't show it.
-	   XXX This is wrong, but too many callers rely on this behavior. */
-	if (err != 0)
-		PyErr_Clear();
 }
 
 PyObject *