From ed5078c9955c3b20c92751b2d2beb0ea8006a3c3 2011-10-14 20:58:15 From: MinRK Date: 2011-10-14 20:58:15 Subject: [PATCH] protect IPython from bad custom exception handlers Previously, errors in custom handlers would result in the custom exception handler's error being printed in lieu of the real exception, and certain cases could cause infinite loops. Now, if CustomTB fails it is unregistered immediately, and the original TB is also displayed. IPython's own BdbQuit_IPython_excepthook had an invalid signature, which revealed this issue, and has also been fixed. test included. closes #692 --- diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index d7b22a3..5992b8e 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -64,7 +64,7 @@ def BdbQuit_excepthook(et,ev,tb): else: BdbQuit_excepthook.excepthook_ori(et,ev,tb) -def BdbQuit_IPython_excepthook(self,et,ev,tb): +def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None): print 'Exiting Debugger.' diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index ad8ebab..0a52cca 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1478,16 +1478,30 @@ class InteractiveShell(SingletonConfigurable, Magic): assert type(exc_tuple)==type(()) , \ "The custom exceptions must be given AS A TUPLE." - def dummy_handler(self,etype,value,tb): + def dummy_handler(self,etype,value,tb,tb_offset=None): print '*** Simple custom exception handler ***' print 'Exception type :',etype print 'Exception value:',value print 'Traceback :',tb #print 'Source code :','\n'.join(self.buffer) - if handler is None: handler = dummy_handler - - self.CustomTB = types.MethodType(handler,self) + if handler is None: + wrapped = dummy_handler + else: + def wrapped(self,etype,value,tb,tb_offset=None): + try: + return handler(self,etype,value,tb,tb_offset=tb_offset) + except: + # clear custom handler immediately + self.set_custom_exc((), None) + print >> io.stderr, "Custom TB Handler failed, unregistering" + # show the exception in handler first + stb = self.InteractiveTB.structured_traceback(*sys.exc_info()) + print >> io.stdout, self.InteractiveTB.stb2text(stb) + print >> io.stdout, "The original exception:" + self.showtraceback((etype,value,tb), tb_offset=tb_offset) + + self.CustomTB = types.MethodType(wrapped,self) self.custom_exceptions = exc_tuple def excepthook(self, etype, value, tb): diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index 1ed515a..9e7b848 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -146,3 +146,21 @@ class InteractiveShellTestCase(unittest.TestCase): finally: # Reset compiler flags so we don't mess up other tests. ip.compile.reset_compiler_flags() + + def test_bad_custom_tb(self): + """Check that InteractiveShell is protected from bad custom exception handlers""" + ip = get_ipython() + from IPython.utils import io + save_stderr = io.stderr + try: + # capture stderr + io.stderr = StringIO() + ip.set_custom_exc((IOError,),lambda etype,value,tb: None) + self.assertEquals(ip.custom_exceptions, (IOError,)) + ip.run_cell(u'raise IOError("foo")') + self.assertEquals(ip.custom_exceptions, ()) + self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) + finally: + io.stderr = save_stderr + +