implement chained exception tracebacks

patch from Antoine Pitrou #3112
diff --git a/Python/_warnings.c b/Python/_warnings.c
index 23223fa..e9384ca 100644
--- a/Python/_warnings.c
+++ b/Python/_warnings.c
@@ -256,7 +256,6 @@
     Py_XDECREF(name);
 
     /* Print "  source_line\n" */
-    PyFile_WriteString("  ", f_stderr);
     if (sourceline) {
         char *source_line_str = PyUnicode_AsString(sourceline);
         while (*source_line_str == ' ' || *source_line_str == '\t' ||
@@ -267,7 +266,7 @@
         PyFile_WriteString("\n", f_stderr);
     }
     else
-        Py_DisplaySourceLine(f_stderr, PyUnicode_AsString(filename), lineno);
+        Py_DisplaySourceLine(f_stderr, PyUnicode_AsString(filename), lineno, 2);
     PyErr_Clear();
 }
 
diff --git a/Python/errors.c b/Python/errors.c
index ac64b6a..3b86c48 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -84,8 +84,23 @@
 				return;
 			value = fixed_value;
 		}
-		Py_INCREF(tstate->exc_value);
-		PyException_SetContext(value, tstate->exc_value);
+		/* Avoid reference cycles through the context chain.
+		   This is O(chain length) but context chains are
+		   usually very short. Sensitive readers may try
+		   to inline the call to PyException_GetContext. */
+		if (tstate->exc_value != value) {
+			PyObject *o = tstate->exc_value, *context;
+			while ((context = PyException_GetContext(o))) {
+				Py_DECREF(context);
+				if (context == value) {
+					PyException_SetContext(o, NULL);
+					break;
+				}
+				o = context;
+			}
+			Py_INCREF(tstate->exc_value);
+			PyException_SetContext(value, tstate->exc_value);
+		}
 	}
 	if (value != NULL && PyExceptionInstance_Check(value))
 		tb = PyException_GetTraceback(value);
@@ -160,6 +175,9 @@
 
 /* Used in many places to normalize a raised exception, including in
    eval_code2(), do_raise(), and PyErr_Print()
+
+   XXX: should PyErr_NormalizeException() also call
+	    PyException_SetTraceback() with the resulting value and tb?
 */
 void
 PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
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 *
diff --git a/Python/traceback.c b/Python/traceback.c
index 76e22a1..55300fc 100644
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -129,7 +129,7 @@
 }
 
 int
-Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno)
+Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno, int indent)
 {
 	int err = 0;
 	FILE *xfp = NULL;
@@ -139,8 +139,6 @@
 
 	if (filename == NULL)
 		return -1;
-	/* This is needed by Emacs' compile command */
-#define FMT "  File \"%.500s\", line %d, in %.500s\n"
 	xfp = fopen(filename, "r" PY_STDIOTEXTMODE);
 	if (xfp == NULL) {
 		/* Search tail of filename in sys.path before giving up */
@@ -203,12 +201,27 @@
 		} while (*pLastChar != '\0' && *pLastChar != '\n');
 	}
 	if (i == lineno) {
+		char buf[11];
 		char *p = linebuf;
 		while (*p == ' ' || *p == '\t' || *p == '\014')
 			p++;
-                    err = PyFile_WriteString(p, f);
-                    if (err == 0 && strchr(p, '\n') == NULL)
-                            err = PyFile_WriteString("\n", f);
+
+		/* Write some spaces before the line */
+		strcpy(buf, "          ");
+		assert (strlen(buf) == 10);
+		while (indent > 0) {
+			if(indent < 10)
+				buf[indent] = '\0';
+			err = PyFile_WriteString(buf, f);
+			if (err != 0)
+				break;
+			indent -= 10;
+		}
+
+		if (err == 0)
+			err = PyFile_WriteString(p, f);
+		if (err == 0 && strchr(p, '\n') == NULL)
+			err = PyFile_WriteString("\n", f);
 	}
 	fclose(xfp);
 	return err;
@@ -228,7 +241,7 @@
 	err = PyFile_WriteString(linebuf, f);
 	if (err != 0)
 		return err;
-        return Py_DisplaySourceLine(f, filename, lineno);
+        return Py_DisplaySourceLine(f, filename, lineno, 4);
 }
 
 static int