Permalink
Browse files

Merge remote-tracking branch 'upstream/master'

  • Loading branch information...
2 parents c0c597c + 71ae352 commit ad500830627eecd1fa6152b213045a178bdab8de Felix Werner committed Oct 17, 2011
@@ -27,6 +27,7 @@
# Imports
#-----------------------------------------------------------------------------
+import atexit
import glob
import logging
import os
@@ -156,6 +157,9 @@ def init_crash_handler(self):
"""Create a crash handler, typically setting sys.excepthook to it."""
self.crash_handler = self.crash_handler_class(self)
sys.excepthook = self.crash_handler
+ def unset_crashhandler():
+ sys.excepthook = sys.__excepthook__
+ atexit.register(unset_crashhandler)
def _ipython_dir_changed(self, name, old, new):
if old in sys.path:
@@ -38,7 +38,7 @@
has_pydb = False
prompt = 'ipdb> '
#We have to check this directly from sys.argv, config struct not yet available
-if '-pydb' in sys.argv:
+if '--pydb' in sys.argv:
try:
import pydb
if hasattr(pydb.pydb, "runl") and pydb.version>'1.17':
@@ -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.'
@@ -104,8 +104,8 @@ def __init__(self,colors=None):
"""
try:
- ip = ipapi.get()
- except:
+ ip = get_ipython()
+ except NameError:
# Outside of ipython, we set our own exception hook manually
BdbQuit_excepthook.excepthook_ori = sys.excepthook
sys.excepthook = BdbQuit_excepthook
@@ -1447,29 +1447,37 @@ def set_custom_exc(self, exc_tuple, handler):
Set a custom exception handler, which will be called if any of the
exceptions in exc_tuple occur in the mainloop (specifically, in the
- run_code() method.
+ run_code() method).
- Inputs:
+ Parameters
+ ----------
+
+ exc_tuple : tuple of exception classes
+ A *tuple* of exception classes, for which to call the defined
+ handler. It is very important that you use a tuple, and NOT A
+ LIST here, because of the way Python's except statement works. If
+ you only want to trap a single exception, use a singleton tuple::
- - exc_tuple: a *tuple* of valid exceptions to call the defined
- handler for. It is very important that you use a tuple, and NOT A
- LIST here, because of the way Python's except statement works. If
- you only want to trap a single exception, use a singleton tuple:
+ exc_tuple == (MyCustomException,)
- exc_tuple == (MyCustomException,)
+ handler : callable
+ handler must have the following signature::
- - handler: this must be defined as a function with the following
- basic interface::
+ def my_handler(self, etype, value, tb, tb_offset=None):
+ ...
+ return structured_traceback
- def my_handler(self, etype, value, tb, tb_offset=None)
- ...
- # The return value must be
- return structured_traceback
+ Your handler must return a structured traceback (a list of strings),
+ or None.
- This will be made into an instance method (via types.MethodType)
- of IPython itself, and it will be called if any of the exceptions
- listed in the exc_tuple are caught. If the handler is None, an
- internal basic one is used, which just prints basic info.
+ This will be made into an instance method (via types.MethodType)
+ of IPython itself, and it will be called if any of the exceptions
+ listed in the exc_tuple are caught. If the handler is None, an
+ internal basic one is used, which just prints basic info.
+
+ To protect IPython from crashes, if your handler ever raises an
+ exception or returns an invalid result, it will be immediately
+ disabled.
WARNING: by putting in your own exception handler into IPython's main
execution loop, you run a very good chance of nasty crashes. This
@@ -1478,16 +1486,62 @@ def my_handler(self, etype, value, tb, tb_offset=None)
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)
+
+ def validate_stb(stb):
+ """validate structured traceback return type
+
+ return type of CustomTB *should* be a list of strings, but allow
+ single strings or None, which are harmless.
+
+ This function will *always* return a list of strings,
+ and will raise a TypeError if stb is inappropriate.
+ """
+ msg = "CustomTB must return list of strings, not %r" % stb
+ if stb is None:
+ return []
+ elif isinstance(stb, basestring):
+ return [stb]
+ elif not isinstance(stb, list):
+ raise TypeError(msg)
+ # it's a list
+ for line in stb:
+ # check every element
+ if not isinstance(line, basestring):
+ raise TypeError(msg)
+ return stb
+
+ if handler is None:
+ wrapped = dummy_handler
+ else:
+ def wrapped(self,etype,value,tb,tb_offset=None):
+ """wrap CustomTB handler, to protect IPython from user code
+
+ This makes it harder (but not impossible) for custom exception
+ handlers to crash IPython.
+ """
+ try:
+ stb = handler(self,etype,value,tb,tb_offset=tb_offset)
+ return validate_stb(stb)
+ 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:"
+ stb = self.InteractiveTB.structured_traceback(
+ (etype,value,tb), tb_offset=tb_offset
+ )
+ return stb
+
+ self.CustomTB = types.MethodType(wrapped,self)
self.custom_exceptions = exc_tuple
def excepthook(self, etype, value, tb):
@@ -1556,11 +1610,7 @@ def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None,
sys.last_value = value
sys.last_traceback = tb
if etype in self.custom_exceptions:
- # FIXME: Old custom traceback objects may just return a
- # string, in that case we just put it into a list
stb = self.CustomTB(etype, value, tb, tb_offset)
- if isinstance(ctb, basestring):
- stb = [stb]
else:
if exception_only:
stb = ['An exception has occurred, use %tb to see '
@@ -1571,9 +1621,11 @@ def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None,
stb = self.InteractiveTB.structured_traceback(etype,
value, tb, tb_offset=tb_offset)
+ self._showtraceback(etype, value, stb)
if self.call_pdb:
# drop into debugger
self.debugger(force=True)
+ return
# Actually show the traceback
self._showtraceback(etype, value, stb)
@@ -50,6 +50,14 @@
"Enable auto calling the pdb debugger after every exception.",
"Disable auto calling the pdb debugger after every exception."
)
+# pydb flag doesn't do any config, as core.debugger switches on import,
+# which is before parsing. This just allows the flag to be passed.
+shell_flags.update(dict(
+ pydb = ({},
+ """"Use the third party 'pydb' package as debugger, instead of pdb.
+ Requires that pydb is installed."""
+ )
+))
addflag('pprint', 'PlainTextFormatter.pprint',
"Enable auto pretty printing of results.",
"Disable auto auto pretty printing of results."
@@ -146,3 +146,37 @@ def test_future_unicode(self):
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: 1/0)
+ 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
+
+ def test_bad_custom_tb_return(self):
+ """Check that InteractiveShell is protected from bad return types in 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((NameError,),lambda etype,value,tb, tb_offset=None: 1)
+ self.assertEquals(ip.custom_exceptions, (NameError,))
+ ip.run_cell(u'a=abracadabra')
+ self.assertEquals(ip.custom_exceptions, ())
+ self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
+ finally:
+ io.stderr = save_stderr
+
+
@@ -223,7 +223,7 @@ def parse_command_line(self, argv=None):
swallow_next = False
continue
if a.startswith('-'):
- split = a.lstrip('-').split('=')[0]
+ split = a.lstrip('-').split('=')
alias = split[0]
if alias in notebook_aliases:
self.kernel_argv.remove(a)
@@ -342,7 +342,7 @@ def parse_command_line(self, argv=None):
swallow_next = False
continue
if a.startswith('-'):
- split = a.lstrip('-').split('=')[0]
+ split = a.lstrip('-').split('=')
alias = split[0]
if alias in qt_aliases:
self.kernel_argv.remove(a)
Oops, something went wrong.

0 comments on commit ad50083

Please sign in to comment.