Repository: ipython/ipython

Issue:
Crash Ultratraceback/ session history
```
print("\x")
```

In Notebook make ultratraceback not behave : 

```
Traceback (most recent call last):

  File "/Users/matthiasbussonnier/ipython/IPython/kernel/zmq/ipkernel.py", line 378, in execute_request
    shell.run_cell(code, store_history=store_history, silent=silent)

  File "/Users/matthiasbussonnier/ipython/IPython/core/interactiveshell.py", line 2676, in run_cell
    self.showsyntaxerror()

  File "/Users/matthiasbussonnier/ipython/IPython/core/interactiveshell.py", line 1774, in showsyntaxerror
    stb = self.SyntaxTB.structured_traceback(etype, value, [])

  File "/Users/matthiasbussonnier/ipython/IPython/core/ultratb.py", line 1205, in structured_traceback
    if isinstance(value.filename, py3compat.string_types) \

AttributeError: 'exceptions.ValueError' object has no attribute 'filename'


ERROR! Session/line number was not unique in database. History logging moved to new session 13867
```

Of course not hightlighted, kernel does not dies, but no prompt number returned.

(that's not master as I merged a few branch in it, but it shoudln't have any effect, and I can't test on master now)



Discussion:
My fault. I assumed that `SyntaxTB` would only ever be called with a SyntaxError, but it's possible for it to be called with another error, such as a ValueError in this case.

Not yours only, went through review (I guess) nobody saw it.


Resolving patch:
diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py
--- a/IPython/core/ultratb.py
+++ b/IPython/core/ultratb.py
@@ -1202,7 +1202,8 @@ def structured_traceback(self, etype, value, elist, tb_offset=None,
         # If the source file has been edited, the line in the syntax error can
         # be wrong (retrieved from an outdated cache). This replaces it with
         # the current value.
-        if isinstance(value.filename, py3compat.string_types) \
+        if isinstance(value, SyntaxError) \
+                and isinstance(value.filename, py3compat.string_types) \
                 and isinstance(value.lineno, int):
             linecache.checkcache(value.filename)
             newtext = ulinecache.getline(value.filename, value.lineno)


Test patch:
diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py
--- a/IPython/core/tests/test_ultratb.py
+++ b/IPython/core/tests/test_ultratb.py
@@ -138,3 +138,12 @@ def test_changing_py_file(self):
             # The SyntaxError should point to the correct line
             with tt.AssertPrints(["7/", "SyntaxError"]):
                 ip.magic("run " + fname)
+
+    def test_non_syntaxerror(self):
+        # SyntaxTB may be called with an error other than a SyntaxError
+        # See e.g. gh-4361
+        try:
+            raise ValueError('QWERTY')
+        except ValueError:
+            with tt.AssertPrints('QWERTY'):
+                ip.showsyntaxerror()
diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py
--- a/IPython/testing/tools.py
+++ b/IPython/testing/tools.py
@@ -357,6 +357,9 @@ def __enter__(self):
         setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
     
     def __exit__(self, etype, value, traceback):
+        if value is not None:
+            # If an error was raised, don't check anything else
+            return False
         self.tee.flush()
         setattr(sys, self.channel, self.orig_stream)
         printed = self.buffer.getvalue()
@@ -375,6 +378,9 @@ class AssertNotPrints(AssertPrints):
     
     Counterpart of AssertPrints"""
     def __exit__(self, etype, value, traceback):
+        if value is not None:
+            # If an error was raised, don't check anything else
+            return False
         self.tee.flush()
         setattr(sys, self.channel, self.orig_stream)
         printed = self.buffer.getvalue()